This commit is contained in:
SG
2025-06-09 23:34:36 +03:00
parent 3348df26e5
commit 2d7f1c1f7d
30 changed files with 1312 additions and 67 deletions

View File

@@ -1,17 +1,12 @@
from datetime import datetime
import os
import shutil
import markdown
import frontmatter
from jinja2 import Environment, FileSystemLoader
from pathlib import Path
from logger import logger
from config import Config
# Prepare template rendering engine
env = Environment(loader=FileSystemLoader(Config.TEMPLATE_DIR))
env.globals['header_image'] = Config.HEADER_IMAGE
index_template = env.get_template("index.html")
content_item_template = env.get_template("content_item.html")
from jinja_env import env, content_item_template, index_template
class DefaultFrontmatterHeader:
def __init__(self, title):
@@ -24,35 +19,115 @@ class DefaultFrontmatterHeader:
class ContentItemPrototype:
def render_content(self):
logger.debug(f"Rendering {self.source_filename} to {Config.OUTPUT_DIR}/{self.target_filename}")
if self.image_src_file.exists():
if self.image_file.exists():
shutil.copyfile(self.image_src_file, Path(Config.OUTPUT_DIR) / self.image)
if self.custom_css_src_file.exists():
if self.css_file.exists():
shutil.copyfile(self.custom_css_src_file,Path(Config.OUTPUT_DIR) / self.custom_css)
if self.custom_js_src_file.exists():
if self.js_file.exists():
shutil.copyfile(self.custom_js_src_file, Path(Config.OUTPUT_DIR) / self.custom_js)
with self.content_output_path.open("w", encoding="utf-8") as f:
with self.target_filename.open("w", encoding="utf-8") as f:
f.write(content_item_template.render(content_item = self, page_title = self.title))
def __init__(self, md_file):
logger.debug(f"Parsing {md_file}")
self.source_filename = md_file
self.slug = md_file.stem
self.target_filename = self.slug + ".html"
self.data = frontmatter.load(md_file)
self.html = markdown.markdown(self.data.content)
self.preview = self.html[:300] + "<a href=" + f"{self.slug}.html" + ">... read more</a>"
self.title = self.data.get("title", self.slug)
self.omit_second_title = self.data.get("omit_second_title", False)
self.date = self.data.get("data", "2000-01-01T00:00:00+03:00")
self.content_output_path = Path(Config.OUTPUT_DIR) / f"{self.slug}.html"
self.image_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.jpg")
self.image = f"images/{md_file.stem}.jpg" if self.image_src_file.exists() else None
self.custom_css_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.css")
self.custom_css = f"static/css/{md_file.stem}.css" if self.custom_css_src_file.exists() else None
logger.debug(f"Custom CSS: {self.slug}: {self.custom_css}")
self.custom_js_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.js")
self.custom_js = f"static/js/{md_file.stem}.js" if self.custom_js_src_file.exists() else None
logger.debug(f"Custom JS: {self.slug}: {self.custom_js}")
# Special handling for the 'about' item
if self.slug == 'about':
self.image = 'static/about.jpg'
def parse_content(self):
try:
self.source_filename = Path(self.source_filename)
self.subdir = self.source_filename.parent
logger.debug(f"Parsing item {self.source_filename}")
self.slug = self.source_filename.stem
self.target_filename = Path(f"{Config.OUTPUT_DIR}/{self.source_filename.parent}/{self.source_filename.stem}.html")
self.data = frontmatter.load(self.source_filename)
self.html = markdown.markdown(self.data.content)
self.url = "<a href=" + f"{self.subdir}/{self.slug}.html" + "> ...read more</a>"
self.preview = self.html[:300] + self.url
self.title = self.data.get("title", self.slug)
self.omit_second_title = self.data.get("omit_second_title", False)
self.date = self.data.get("date", "2000-01-01T00:00:00+03:00")
self.image_file = self.source_filename.stem + ".jpg"
self.css_file = self.source_filename.stem + ".css"
self.js_file = self.source_filename.stem + ".js"
except Exception as e:
logger.error(e)
def create_content(self):
with open(self.source_filename, mode="w", encoding="utf-8") as f:
f.writelines([
"---\n",
self.default_frontmatter_header.title,
self.default_frontmatter_header.date,
self.default_frontmatter_header.description,
self.default_frontmatter_header.author,
self.default_frontmatter_header.omit_second_title,
"---\n"
])
def __init__(self, filename, title = None):
self.source_filename = filename
self.title = title or self.source_filename.stem
self.default_frontmatter_header = DefaultFrontmatterHeader(self.title)
class SitePrototype:
def init_site():
logger.info("Initializing new site")
content_dir = Path(Config.CONTENT_DIR)
static_dir = Path(Config.STATIC_DIR)
templates_dir = Path(Config.TEMPLATES_DIR)
images_dir = Path(f"{Config.STATIC_DIR}/images")
css_dir = Path(f"{Config.STATIC_DIR}/css")
js_dir = Path(f"{Config.STATIC_DIR}/js")
# Create directories
for subdir in [content_dir, static_dir, templates_dir, images_dir, css_dir, js_dir]:
os.makedirs(subdir, exist_ok=True)
# Create templates from literals
import templates
template_names = [t for t in dir(templates) if not t.startswith('_')]
for template_name in template_names:
template_content = getattr(templates, template_name)
with open(templates_dir / f"{template_name}.html", "w", encoding="utf8") as f:
f.write(template_content)
# Create static/about.md
def build_site(self):
# Recreate the output dir if needed
if output_dir.exists():
shutil.rmtree(output_dir)
output_dir.mkdir()
# Create public subdirs
subdirs = [img_dir, css_dir, js_dir]
for subdir in subdirs:
subdir.mkdir(arents=True, exist_ok=True)
# Copy static files if exist
if static_dir.exists():
shutil.copytree(static_dir, output_dir / "static", dirs_exist_ok=True)
# Parse the content files
content_items = []
for md_file in content_dir.glob("*.md"):
current_content_item = ContentItemPrototype(md_file)
current_content_item.render_content()
content_items.append({
"slug": current_content_item.slug,
"title": current_content_item.title,
"date": current_content_item.date,
# "preview": markdown.markdown(current_content_item.preview),
"image": current_content_item.image,
})
# Render the index file
with (output_dir / "index.html").open("w", encoding="utf-8") as f:
f.write(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=content_items))
# Render the about file
about_content = ContentItemPrototype(Path('static/about.md'))
about_content.render_content()
# Move 'robots.txt' into output_dir
shutil.copyfile(static_dir / 'robots.txt', output_dir / 'robots.txt')
logger.info(f"Created {len(content_items)} content items.")
def __init__(self):
self.output_dir = Path(Config.OUTPUT_DIR)

View File

@@ -7,10 +7,7 @@ class Config:
APP_DESCRIPTION = "Simplistic static site generator"
APP_SRC_URL = f"https://git.exocortex.ru/Exocortex/{APP_NAME}"
OUTPUT_DIR = 'public'
OUTPUT_IMG_DIR = f"{OUTPUT_DIR}/images"
OUTPUT_CSS_DIR = f"{OUTPUT_DIR}/css"
OUTPUT_JS_DIR = f"{OUTPUT_DIR}/js"
TEMPLATE_DIR = 'templates'
TEMPLATES_DIR = 'templates'
CONTENT_DIR = 'content'
STATIC_DIR = 'static'
HEADER_IMAGE = 'static/header.jpg'

9
content.bak/about.md Normal file
View File

@@ -0,0 +1,9 @@
---
title: 'about'
date: '2025-06-08 19:18:24'
description:
author:
omit_second_title: 'False'
---

BIN
content.bak/galatea.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

82
content.bak/galatea.md Normal file
View File

@@ -0,0 +1,82 @@
---
date: '2025-06-04T13:24:45+03:00'
title: 'Галатея'
---
Самозабвенно, руки в кровь сбивая
О грубый и холодный мрамор,
Мечту прекрасную ваяешь,
Достойную великих храмов
Бессмертный совершенный образ
Творишь, богами вдохновлённый,
Изгибы нежные рисует
Резец твой, музами ведомый
И вот она на пьедестале застыла,
Будто время встало
Хитона складки приоткрылись,
Бедро игриво обнажая
Зовет, не издавая звуков,
Рукою белоснежной манит
— Иль это только наважденье
Неверным разумом играет?
Слова любви, молитвы, слезы,
Подарки, жертвы и проклятья
Все тщетно: это просто мрамор,
Глупец! Какое с камнем счастье?!
Прочь! Вон из дома! В храм скорее!
Пред Афродитой преклониться:
Гимн в камне высек для тебя я —
Так пусть он в плоть переродится!
Перед тобой разверзлись волны
— Пред ней скала пусть растворится
Ты всех богов в себя влюбляешь,
А я… в творенье рук влюбился!
Святые своды звон наполнил
И заискрился воздух нежно:
“Ты удивил меня, Художник”, —
Богиня молвила небрежно, —
“Ты силой чувств меня растрогал,
Развеял скуку дней бессчетных.
Я дам тебе то, что ты просишь,
Чтоб не погиб в мечтах бесплотных”
Он, словно громом пораженный,
Назад в беспамятстве вернулся
Упал в бессильи у порога
И в сон глубокий окунулся
Вот Гелий жаркими лучами
Сна наважденье прогоняет
Лицо рукой прикрыв от света,
Глаза ваятель открывает
Убранство скромного жилища
Окинул взором безнадежным
И видит: нимфа примеряет
Пред зеркалом свои одежды
Боясь спугнуть оживший морок,
Он молвил шёпотом, робея,
Не веря в чудо Афродиты:
“Взгляни в глаза мне, Галатея!”
Воздушно дева обернулась,
Рукою локоны поправив,
Прошла, словно не видя, мимо
Его в безмолвии оставив
“Постой! Моей ты страстью дышишь”,
Схватил её он за запястье…
Все тщетно: это просто мрамор,
Глупец! Какое с камнем счастье?!

147
content.bak/giotto.md Normal file

File diff suppressed because one or more lines are too long

147
content.bak/giotto.md~ Normal file

File diff suppressed because one or more lines are too long

BIN
content.bak/lone_raider.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@@ -0,0 +1,99 @@
---
date: '2025-06-04T13:24:45+03:00'
title: 'Призрачный всадник'
---
Слова мои, как кисть, рисуют на холсте небес
Холмов высоких очертанья, острой гранью лес
Далекий горизонт разрезал пополам
Вглядись туда, и ты его увидишь там…
На тех холмах, в мерцающих лучах заката,
Огромный силуэт верхом, закован в латы
По ветру плещет длинный темный его плащ
О нем я поведу мой долгий грустный сказ.
Когда луна секреты шепчет в ухо ночи,
И ветер легкий этот шепот вдаль уносит,
Тень рыцаря, одетого в холодный звездный свет,
На призрачном коне, пронзает взором бездну лет
В доспехах черных и холодных, словно лед
Безмолвно чрез века свое проклятие несет
Подписан кровью страшный договор печали,
Закована душа в броню из твердой стали
Он вел вперед войска под знаменем с крестом
Разил врагов своим безжалостным мечом
Геенной огненной кругом пылали битвы
И Смерть взимала дань, под стоны и молитвы
За Гордость бился он, за Долг, за Короля,
— И реки крови не могла впитать зе
Неистовым копьем освобождал он мавров души
Рыдали ангелы, не в силах крики слушать
Однажды, средь руин и сотен мертвых тел
Лик девы он прекрасной бездыханной разглядел
И сердце рыцаря на миг остановилось:
Красивее лица во снах ему не снилось!
“О! Мой Господь! Не за тебя ль всю жизнь я бился?!
И меч мой верный меж врагов твоих ярился
Твой светлый стяг чрез все пустыни мира нес,
Не ведая сомнений, жалости, и слез!
Молю тебя: прости, что жизнь забрал
У Ангела, мой Бог! Клянусь, ведь я не знал!”
В ответ лишь тишина — и едкий крик ворон…
Накрыл плащом и рядом лег, убитый горем, он
Вот ночь спустилась пологом тяжелым, душным
И небо замерцало россыпью жемчужной
Ленивый месяц белый томно на боку лежит
У стен разрушенных Христово войско спит
Средь минаретов и домов сгустились тени
Встал рыцарь возле девы на колени
Сжал лезвие меча своей ладонью грубой
Кровь пролилась в навеки сомкнутые губы
“Раз Бог не хочет дать мне то, о чем молю
Иными силами я деву оживлю!
Пусть Господа завет святой нарушу,
Отдам я Дьяволу мятущуюся душу!
И с трепетом ресниц раскрылись веки
Глаза блеснули, словно в полнолунье реки
Откинув плащ его, на локте поднялась
Вздохнула и к нему всем телом подалась
Всмотрелась в незнакомый гордый лик
И вдруг кинжал изогнутый в руке ее возник
Как молния, сверкнул, как ветра дуновенье
Застыл у горла шевалье он на мгновенье
И бархат неба вспыхнул ярким тут огнем
И стало вдруг светло на миг, как будто днем…
И алой кровью оросив алтарь руин,
Руками горло сжав, пал навзничь паладин
С тех самых пор, невольник клятвы страстной,
Скитается в ночи’, эпохам неподвластный
В лесах дремучих и на сумрачных холмах
В пустынях призрачных, в безмолвных страшных с
Без устали и без надежды смотрит вдаль
Фигура рыцаря того, закованная в сталь
И ветер тихо шепчет паладина имя,
Когда при свете звезд земля от жара стынет…
Слова мои, как кисть, рисуют на холсте небес
Холмов высоких очертанья, острой гранью лес
Далекий горизонт разрезал пополам
Вглядись туда, и ты его увидишь там…
На тех холмах, в тускнеющих лучах заката,
Огромный силуэт верхом, закован в латы
Навечно проклят он бродить по свету,
Покуда помнят менестрели песню эту

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -0,0 +1,113 @@
---
date: '2025-06-04T13:21:41+03:00'
title: 'Персидская ночь'
---
Над куполами ветер тихо шелестит в ночи
По стенам вьются кружева хуруфа
И месяц возлежит на ложе из парчи
Спит Шахин-Шах на шелке среди наложниц юных
На лбу его разгладились морщины
А вдалеке задумчиво поет канун
Чернее тьмы, шайтана породившей,
Крадется ассасин
— Один
Ковры и шкуры эхо глушат в длинных коридорах
Свет факелов в углах играет тенью
Негромкий стража хрип и тихий шорох,
Когда изогнутая сталь, чуть взвизгнув, взрежет тьму,
Бесшумно на ковер осядет тело
Небрежно на пол уронив с главы чалму
Давно визирь коварный плел узоры козней злых
И вот решил: пора владыке на покой
К советам стал он глух и скуп на похвалы
Совсем забыл он о родне, неблагодарным стал
И не дает в карман припрятать злато
Где ж это видано?! Пусть сдохнет, как шакал!
Чернее тьмы, шайтана породившей,
Крадется ассасин
— Один
Не скрипнет дверь, лишь пес огромный дернет ухом
Подкупленных наложниц след простыл
Скользнул убийца внутрь бесплотным духом
Пес первым был убит — мелькнул кинжал скупым размахом
И ассасин клинок из тонкой стали
Занёс над сердцем беззащитным шаха
Остановилось время на мгновенье,
До смерти только вдох
— Один
У изголовья силуэтом загустел Эфир
Ужасным Индрой, демоном насилья
"Бессмертная" возникла — и сверкнул шамшир
Движеньем плавным в миг с кинжалом руку отсекла
Залив шелк простыней обильно кровью
Под подбородком нежно сталью провела
Встал Шахов Шах, в руке сжимая золоченый меч.
"Прости, мой Царь, что сон твой потревожен.
Пёс мертв, но жизнь твою смогла я уберечь"
"Прощенья не проси, ведь ты достойна высшей чести
Мой бедный пёс... Он верным другом был мне
А этот душегуб — лишь раб. Я жажду мести!"
Слуг, Дана, позови и всё здесь прикажи убрать
А тело отнеси к визирю в спальню
Изменнику бесчестному подбрось в кровать!
И поутру его ко мне на завтрак притащи — нагим
Ползти заставь его червем по полу
Когда закончу есть, поговорю я с ним
На площади дворца горит костер священный
Шипит, плюясь, огонь душистым маслом
Вот, в клетку заточён, доставлен пленный
Совсем нагой, в крови и нечистотах
Вчера лишь — господин!
— Один
Шах подошел к нему, нахмурился брезгливо
Над площадью тотчас повисла тишина
Все ждут. Время течет неторопливо
"Ну что ж, сатрап, не на того ты замахнулся!
Меня убить отправил ассасина!"
— Шах замолчал, недобро ухмыльнулся
"Мой приговор запомнят люди в целом мире!
Палач с тебя живого снимет кожу!
— Обтянут ею кресло для визирей"
"Воительница Дана, жизнь спасшая Царю!
За верность твою в дар прими мой меч—шамшир
На острой глади — золотом: "Благодарю!"
"Я все сказал. Пусть мой приказ исполнят."
Промолвил и ушел.
— Один
Над куполами ветер тихо шелестит в ночи
По стенам вьются кружева хуруфа
И месяц возлежит на ложе из парчи
На бортике фонтана — фигура грустная Царя
Сидит один и тяжело вздыхает
Потрескивает тихо масло фонаря...
Правителей судьба незавидна, увы.
Мечты о подвигах, любви и славе,
А наяву — интриги, козни, в чаше — яд
И голова болит от мыслей о державе

9
content.bak/test2.md Normal file
View File

@@ -0,0 +1,9 @@
---
title: 'test2'
date: '2025-06-08 20:16:33'
description:
author:
omit_second_title: 'False'
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -0,0 +1,3 @@
document.addEventListener('DOMContentLoaded', function() {
console.log("Custom JS loaded");
}, false);

299
content.bak/whole_summer.md Normal file
View File

@@ -0,0 +1,299 @@
---
title: "Целое лето"
omit_second_title: False
---
<small style="font-family: Lucida Console; font-size: 0.8em;">
By reason or force / Stripe summer / Schools out and so are you
</small>
<br>
Далеко внизу лязг контейнеров, грохот техники и выкрики рабочих сливались в равномерный гул. Порт Вальпараисо был довольно тихим в звуковом диапазоне. Его настоящий шум был электронным.
И уровень этого шума зашкаливал. Не только пристань для множества судов, от гигантских лайнеров до неприметных частных яхт без иллюминаторов порт, с его скоростными соединениями, оптоволоконными линиями и спутниковыми аплинками, представлял собой огромный информационный хаб. Всем требовалась связь. Конечно, каждый хотел знать, о чем все говорят, и все понимали, что каждый пытается подслушивать. Порт Вальпараисо постоянно находился в центре пристального внимания. На него были направлены сотни антенн, сенсоров и сканеров. Так много, что один человек с портативным оборудованием легко оставался незамеченным.
Маленький наушник щелкнул, оживая, и голос Реджи произнес:
— Как успехи? Какие удивительные известия ждут нас в киберпространстве?
Его голос вызывал в воображении твидовый пиджак, английскую кепку, возможно, молоток для игры в крокет голос человека, который владеет несколькими поместьями, и никогда их не посещает.
— Надо было взять бикини. — ответила Ольга, щурясь от жгущего солнца.
— Сейчас январь.
— И оказывается, здесь лето! Ты знал?
— Я забыл. — сказал Реджи.
— Я тоже. Вот про часовые пояса я всегда помню. — сказала Ольга.
— Часовые пояса еще хуже. Из-за часовых поясов я никогда не путешествую. — сказал Реджи.
— Правда?
— Правда. Менять часовой пояс вредно для мозга.
— Джетлаг? Проблемы со сном?
— Проблемы со _снами_, — уточнил Реджи. — Перемещение в другой часовой пояс нарушает сны.
— Это джетлаг.
— Это не джетлаг. Слушай, ты когда-нибудь задумывалась, как появились разные религии и почему одни так похожи на другие?
— Честно говоря, не особенно. Подожди.
Ольга склонилась над портативным терминалом, на экране которого мерцали строчки сообщений.
— Что там? — спросил Реджи.
— Ничего. Показалось.
— Очень жаль.
— Продолжай. Джетлаг и различные религии.
Не джетлаг.
— Хорошо, не джетлаг.
— Так вот, часовые пояса влияют на наши сны. Тем, кто в одном часовом поясе, снятся схожие сны.
— Ты только что это выдумал?
— Это наука, друг мой.
— Наука утверждает, что люди в одном часовом поясе видят одни и те же сны?
— Структурно схожие, не одинаковые.
— И почему так происходит?
Ну, предположим, сны, которые нам снятся, представляют собой бесконечное счётное множество.
— Что?
— Бесконечное счётное множество. Плохая связь? Я пропадаю?
Ольга засмеялась.
— Нет, ты не пропадаешь. Продолжай.
— Отобразим это множество на время суток, когда мы спим. Тогда часовой пояс становится порождающим элементом. Это особый элемент, который...
— Генератор.
— Генератор. — согласился Реджи.
— Я знаю, что такое генератор в алгебраической группе.
— Я знаю, что ты знаешь, но повторить лишний раз стоило. Так вот, этот элемент создаёт всю группу через операцию, скажем, сновидения. Ты же согласна, что сны это не просто случайный процесс, а трансформации, да?
— Да-а. — протянула Ольга.
Она пошевелилась, горячая кровельная черепица скрипнула.
Ольга снова посмотрела на терминал и сказала:
— Подожди.
— Наверно, лучше не отвлекать тебя. — сказал Реджи.
— Наверно. — согласилась Ольга, и потом добавила — Но мы поговорим о снах еще.
— Конечно. — сказал Реджи.
Наушник замолчал.
Она снова сгорбилась над экраном и прокрутила вверх и вниз сообщения, которыми суда обменивались с портом. Сплошной электронный шум, как и бывает в сетях с высоким трафиком. Многие системы были настроены торопливо, сервисными командами, которым не платили по часам. Сотни сообщений отправлялись повторно, снова и снова, из-за ошибок в протоколе. Все зашифрованы и прочесть их невозможно, но Ольга и так знала, что там: заявки на оформление судов, заказы бронирования. Декларации о доставке, подробно описывавшие чьи-то бесчисленные грузы, выделялись из общей массы, как самые аккуратные. Даже контрольные суммы были старательно дополнены. Но даже их кому-то приходилось отправлять несколько раз, прежде чем порт соглашался их принять.
Терминал не показывал ничего, что не было бы новым ложным срабатыванием.
Она уставилась на экран.
Декларации действительно выглядели очень аккуратно, как будто, для разнообразия, те, кто их отправляли, знали протокол, и следовали правилам строго. Очень строго.
Слишком строго.
Ольга сощурилась.
Контрольные суммы было не обязательно дополнять до предельной длины, протоколы этого не требовали. Да и все остальные этим не утруждались.
Она перепроверила.
Дополнения контрольных сумм делали их достаточно длинными, чтобы в них поместились настоящие данные. Если бы кто-нибудь хотел передать сообщение так, чтобы его было нельзя не только прочитать, но даже обнаружить, то в эти тщательно сделанные дополнения как раз можно поместить небольшую шифрограмму.
Она стукнула пальцем по наушнику:
— Я знаю, как они это делают. Они прячут…
— Два “Спринтера” въехали на твою улицу, по одному с каждой стороны. — перебил ее Реджи. Его голос звучал встревоженно. Ольга не любила, когда он тревожился.
— Кто? — быстро спросила она.
Не знаю, но вооружены.
_Сегуридад_?
— Они предупредили бы, но ничего не говорили. Слушай, эти ребята двигаются к твоей позиции.
Последовала очень короткая пауза, потом Реджи сказал:
_Сегуридад_ не знают, что происходит. Надо уходить.
— Можешь не уговаривать.
Ольга захлопнула терминал, запихнула в сумку вместе с антеннами, спрыгнула вниз через люк и побежала к выходу.
Снаружи кулаки в армированных перчатках забарабанили по металлической двери. Кто-то закричал “Полиция! Открывай! олисия эспесьяль_!”
Она выругалась шипящим шепотом, бросилась назад. Забралась на стол, подпрыгнула, схватилась за края люка, подтянулась и снова вылезла на крышу. Под ногами заскрипел асбест и редкий гравий. Она побежала к отвесному склону холма, поднимавшемуся почти из стены здания, и вскарабкалась наверх.
Внизу с лязгающим грохотом упала железная дверь, которую сбили с петель, но Ольга уже бежала вверх по пустой извивающейся улочке. За ее спиной раздались выкрики.
— Поверни налево. — сказал голос Реджи.
Она повернула.
Возможно, это действительно была полиция, но возможно, нет. Плохо и то, и другое. Кто бы это ни был, они преследуют ее. Это еще хуже.
Сумка с оборудованием била по спине.
— Снова налево. — сказал Реджи. — К площади.
Площадь была заполнена людьми. Многие танцевали, стучали барабаны, развевались флаги. Прошла фигура на высоких ходулях, а за ней жонглеры. Какой-то местный праздник или карнавал.
Ольга смешалась с толпой очередная туристка, одетая не по погоде, вся в поту. Танцующие женщины в ярких костюмах и плюмажах из высоких перьев окружили ее, и через минуту исчезли. Совсем рядом кто-то захохотал, почти ей в лицо. Это был счастливый, заразительный смех. Музыка становилась громче. Ольга двигалась в людском потоке несколько кварталов, затем выскользнула в узенький переулок.
— Окей. — сказал Реджи. — Пока чисто.
— Разве это не здорово. — ответила она.
— Давай в сейфхаус. Я пока выясню, что смогу.
— Нет. — сказала Ольга. — Выясни больше.
Никто не пытался ее остановить. Никто даже не шел следом. Вскоре ее уже уносил поезд метро.
Вагон был почти пустой. Ольга разглядывала наклейку на стене флаг, герб и девиз “Разумом или силой” и вспоминала, сколько камер наблюдения ее видели и записали.
Вошли четверо копов в форме. Она глянула на них из-под капюшона, не поворачивая головы. Копы прошли мимо и сели через ряд, продолжая разговор. Судя по обрывкам, которые Ольга могла разобрать, речь шла о перелетных птицах.
Она вышла на ближайшей станции.
Солнце было еще высоко, но тени уже делались длиннее и воздух колебался волнами вечернего жара. Ольга осторожно обошла квартал по кругу, потом еще раз, и только после этого подошла к двери. Наушник щелкнул.
— Это действительно была полиция. — сказал Реджи. — И они все еще тебя ищут. Не лично тебя, конечно. Просто того, кто был там в это время. Примерно твоего роста, примерно в твоей одежде.
— Сразу стало гораздо спокойнее. — ответила она.
Она наконец сняла сумку с терминалом, положила ее на стол и осмотрелась. Стол, стул, кровать сейфхаус был не особенно богат мебелью. Она и не собиралась долго там оставаться.
За окном взвыла сирена полицейской машины, пронеслась рядом и затихла вдали.
— Давай рассуждать. — сказал Реджи. — _Сегуридад_ обещают, что сейчас сделают несколько звонков и буря уляжется, но если честно, я так не думаю. Те ребята приехали, потому что знали, что ты там. Знали, где искать.
— Да, меня тоже это удивило. — сказала Ольга.
— Как будто внезапно появился новый игрок. Или кто-то мутит воду, чтобы проверить, как мы себя поведем. Даже не знаю, что мне нравится меньше.
А есть разница?
— Слушай. — сказал Реджи. — Слушай, в этот раз была вооруженная полиция. В следующий раз будет кто-то ещё хуже. Кажется, на тебя открыли охоту.
— Съездишь в Чили, будет как отпуск, даже не работа. — пробормотала Ольга.
— Вот именно. — согласился Реджи. — Будь осторожной, ладно? Окей, давай теперь хорошие новости. Ты поняла их способ коммуникации. Как это устроено?
Ольга не ответила. У нее возникло чувство, что на нее смотрят. Она резко обернулась.
Наклонив голову на бок, ее пристально разглядывала молодая женщина, одетая в черную форму без опознавательных знаков. Очень загорелая, коротко стриженная, под левым глазом короткие глубокие шрамы, как после установки тактического импланта, ТНИ-2 или что-нибудь поновее.
Но ее лицо Ольга узнала сразу же.
— Марта? — хотела спросить она. — Что ты здесь делаешь?
Хотя все было понятно и так, и стало окончательно ясно, когда в руке Марты появился нож и она метнулась вперед.
Ольга увернулась, прыгнула в сторону, потом в другую, свалила Марту с ног и покатилась с ней по полу, стараясь держать опасную руку подальше. Она оказалась сверху, придавила руку с ножом, ударила Марту в лицо и била до тех пор, пока та не перестала шевелиться. Потом заметила, что вокруг все в крови, и что эта кровь течет из ее бока. Она зажала бок, но кровь шла еще откуда-то. Она слезла с неподвижной Марты, отползла в сторону и прислонилась к стене.
— Ольга, что происходит? Ольга! — голос Реджи доносился как будто откуда-то издалека.
— Подожди. — беззвучно сказала она.
Остановить кровь не получилось. Комната наклонилась вбок и вверх, в глазах стало темнеть, и она отстраненно подумала, что сейчас, наверно, перед ней пронесется вся жизнь, но жизнь не пронеслась, и вообще ничего не случилось. Она только вспомнила, что однажды уже хотела по-настоящему убить Марту очень давно, в другом месте, и, считай, в другой жизни. Причина ссоры забылась, но в тот день, когда они впервые увидели друг друга, Ольга ударила ее. Никто не мог точно сказать, что именно Марта перед этим сделала, хотя все соглашались: это был какой-то плохой поступок, и ударить ее санками по голове было вполне естественно. Произошел скандал, сопровождавшийся громким плачем, несколько слов прозвучали на повышенных тонах, но всему этому недоставало решительности, и кто-то даже сказал "А так ей и надо". Спустя пару дней они снова встретились на детской площадке и после нескольких минут взаимного недоверия выяснили, что обе интересуются динозаврами, и обе согласны, что велоцираптор самый лучший. Вскоре было объявлено, что они пойдут в одну школу и будут учиться в одном классе.
Школа оказалась самым интересным на свете но только на первое время. Через три или четыре года новизна ушла, и непонятным образом получилось, что это место скучное и не очень-то приятное. Ольга поняла, что предпочитает лето, главным образом потому что не надо сидеть на уроках, и не надо ходить потом в спортивную секцию. Конечно, Марте доставалось даже больше, у нее в придачу ко всему по субботам были еще и занятия танцами. Танцы Марта презирала и, как сама говорила, лучше бы пошла в тир. Она даже сделала рогатку и постоянно носила ее с собой. В общем, и та, и другая считали дни до конца учебного года.
Кроме того, летом ездили в деревню, и, в отличие от города, жизнь там была очень насыщенная.
Там имелся кинотеатр. Обычно зрительный зал был закрыт, зато у входа стояли игровые автоматы. Их никогда не включали, но кнопки нажимались и рули крутились, так что если применить воображение, весь процесс ничем не отличался от настоящей игры.
Рядом был маленький парк аттракционов. Можно было кататься на карликовом колесе обозрения, только кому-то приходилось забраться на коробку с мотором и крутить ремень передач.
Также имелся песчаный карьер, и еще огромная ржавая труба лежала прямо посреди луга. Насчет происхождения и назначения трубы строились самые невероятные догадки и гипотезы. Наиболее научная утверждала, что это часть космической ракеты, упавшая с орбиты. Идея принадлежала Марте, и косвенные подтвержения этой версии приходилось признать. Впрочем, собственное предположение Ольги труба должна стать частью длинного подземного тоннеля под рекой было не хуже.
В любом случае, труба была хорошей полезной вещью. Можно было ходить по ней и в ней, кидать камни или кричать внутрь, чтобы получалось эхо. Если же каких-нибудь двух пешеходов, гулявших по лугу, заставал дождь на пути домой, в трубе им находилось укрытие.
Других труба тоже привлекала. Это была компания соседских, по большей части знакомых, которых регулярно встречали по дороге на реку или в так называемый центр. С ними не дружили, но и не враждовали, обе стороны признавали взаимное существование, и оставляли друг друга в покое.
По большей части.
Однажды, когда уже надо было уходить из парка с аттракционами и идти обедать, эта компания появилась, как бы принимая пост на микроскопическом колесе обозрения. Ольга молча глянула на них, как делала всегда, и ей ответили тем же, а когда расстояние достаточно увеличилось, было выкрикнуто какое-то слово и все засмеялись. Это слово Ольге было неизвестно, но звучало оно не очень, а потом его повторили еще раз и все снова засмеялись. Ольга повернулась к Марте:
А что это значит?
Та была бледная от злости.
В руках у неё появилась рогатка, в воздухе свистнуло. Кто-то коротко вскрикнул.
Марта стала белая как бумага. Быстрыми шагами она подошла к компании. Те попятились. Один из них, самого низкого роста, зажимал лицо ладонью. Она силой убрала его руку. Глаз был цел, под ним набухал маленький багровый след в форме подковы.
Не ной, все нормально. — бросила она, и повернулась к Ольге. — Пошли. Пошли, все нормально.
Но все было далеко не нормально. Очень скоро на них уже громко кричали. Упоминалась милиция. Пострадавшего звали Марк, и его лишь чудом не покалечили. Марту посадили под домашний арест. Ей запретили выходить из дома, и даже из комнаты. Рогатку отобрали и уничтожили.
— Я все равно новую собиралась сделать. — пробурчала она.
Ольга тоже провела некоторое время в комнате, но ей сказали, что она-то ни в чем не виновата и может ходить куда угодно. Вечером ей даже сказали:
— “Вспомнить всё” идет. Будешь смотреть? Вы хотели.
— Мы потом посмотрим. — вежливо ответила она.
Утром она подкараулила Марка, когда тот был один, и молча разбила ему нос. На следующий день она сделала то же самое, а на третий Марта остановила ее в дверях.
Не бей его больше. — сказала она.
— Почему это? — спросила Ольга. — Пусть родителям не стучит.
— Потому это. — сказала Марта. — Во-первых, он не стучал. Он сказал им, что разбирал велосипед, и что-то отлетело.
— И ты поверила?
— Да. — сказала Марта. — А во-вторых, он сказал, что тоже тебе даст. Что тебе мало не покажется.
Ольга фыркнула.
— Пусть попробует.
А в третьих, мы помирились.
Это было отвратительное, низкое предательство, но Ольга только равнодушно сказала:
Все понятно.
— Что тебе понятно? — спросила Марта.
Все мне понятно.
Через несколько дней они пошли в старый карьер, забрались на самую высокую вершину, и ногами сталкивали вниз куски слежавшегося песка, чтобы получались лавины. Лавины сметали все на пути, падали на воображаемые города и деревни внизу и оставляли только руины.
Не придет. — сказала Ольга. — Пошли лучше в парк.
— Придет. — ответила Марта.
— Орел придет, решка не придет. — Ольга бросила монетку и посмотрела, что выпало.
— Решка. — она продемонстрировала результат, показывая, что не жульничает. — Не придет.
— Придет. — ответила Марта.
Внизу появилась маленькая фигурка.
— Фома неверующая. — сказала Марта, и поехала по склону.
Ольга немного посидела на вершине в одиночестве, потом встала на ноги и пошла вниз.
Маленькая фигурка ждала и не двигалась.
Ольга остановилась в паре шагов. Они посмотрели друг на друга.
— Можешь мне тоже врезать, если хочешь. — сказала Ольга.
— Я человек интеллектуальной культуры. — ответил Марк.
А вообще умеешь? — спросила Ольга.
Тот пожал плечами.
— Пойдете на свалку? — предложил он.
А что там? — Марта сняла с ноги кроссовок и вытряхнула песок.
— Разное. Дохлые коровы. И бывают пузыри из газа. Их можно поджигать если есть чем. Только это за шоссе, вон туда. — Марк показал вдаль рукой.
Ольга переглянулась с Мартой.
Поджечь газ не удалось, и дохлых коров не видели. Они обнаружили лужу какой-то омерзительной жижи, которая тоже не загорелась. Еще им попались две курицы с отрезанными головами.
— Вымерли и лежат в грунте, как динозавры. — сказал Марк.
А они и есть динозавры. — возразила Марта.
Конечно, все это не годилось для школьного сочинения. Там полагалось писать о восходах и закатах, и о прогулках в лесу. Раньше Ольга пыталась спорить с этими правилами, но со временем перестала. Так что особенно написать было нечего.
Рядом Марта выводила строчку за строчкой каллиграфическим почерком и уже начала третью страницу. Ольга грызла ручку, глядела в окно, через мутное стекло: падающий лист, черно-белая ветка голого дерева, толстая пчела в тяжелом гулком полете.
Другие занятия были такими же безынтересными, за исключением, пожалуй, алгебры, наименее унылой из всех.
Большие надежды возлагались однажды на информатику, и вначале чертить логическую схему было интересно, но затем оказалось, что двадцать человек сидят около выключенных компьютеров и разглядывают использованную старую перфокарту, каждое отверстие которой объясняется в величайшей подробности. После такого даже спортивная секция казалась глотком свежего воздуха.
Наконец Марта смогла дать положению дел точную оценку.
— Слава богу. — сказала она. — Слава богу, можно до сентября теперь об этом дебилизме не думать.
Ага. — сказала Ольга.
Она бросила взгляд в сторону двухэтажного домика в конце улицы.
— Можешь не смотреть, еще не приехали. — сказала Марта.
— Приедут.
— Приедут. — согласилась Марта.
А мы, значит, в этот раз первые.
— Похоже на то.
Тишину этих вечеров нарушали только цикады, которых все, даже удивительно Марта, называли сверчками.
Постепенно шумов делалось больше, в домах зажигались окна, и по ночам застучала музыка.
— Добрый день, девушки. — сказал Марк.
— Наконец-то! — воскликнула Марта — Вот эта заждалась уже. Хочет дать тебе в рожу, видимо.
— Да? — спросили Марк. — А ты?
— Я бы _никогда_!
— Привет. — сказала Ольга. Она заулыбалась и с этим ничего нельзя было поделать.
— Вы как, уже придумали что-нибудь на сегодня? — спросил Марк. — А то есть лодка.
— Мы за. — сказала Ольга.
Марта уточнила:
— Моторная? Что за лодка?
— Нет, Марта, не моторная. Это лодка. В которой надо грести веслами.
— Мы за. — сказала Ольга.
Марк посмотрел на неё сверху вниз и подмигнул.
— Настоящий друг. — сказал он.
Марта наклонила голову набок.
А ты когда это так загорел? — спросила она. — Вы же только приехали.
В Египте. — ответил Марк.
В Египте? — переспросила Ольга.
— Ездили в марте на неделю.
— Ничего себе. — сказала Марта.
Она снова отметила про себя, что шрам в конце концов получился очень красивый: в меру широкий, в удачном месте, и похожий на белую змейку.
Давай. согласился Марк.
Не говори так девушкам. сказала Марта. Нам еще можно, мы еще поймем. А другим не говори.
Ольга повернулась в сторону берега, сложила ладони рупором и заорала:
Эй, городские! А ну пошли нахер с моего пляжа!
С берега замахали и закричали что-то в ответ.
— Думаете, трактор когда-нибудь переедет поле? — спросил Марк.
— Какой трактор? — сказала Марта.
Марк кивнул на открытое окно.
— Я не виновата, что у нас окна выходят в эту сторону. — возмутилась Марта.
А я ничего не говорю. — сказала Ольга.
Марта посмотрела на Марка:
— Хотя можем к тебе пойти.
Ольга наклонила голову и высунула кончик языка. От него отделилась ниточка слюны и попала внутрь приставки. Ольга размазала ее пальцем.
— Ты плюешь туда, что ли? — Марта была так поражена, что даже отставила в сторону лак для ногтей.
— Чтобы контакты очистить. Так делают. — ответила Ольга.
— Так делают дебилы. — сказала Марта. — Кстати, знаешь, почему дебилы всегда спрашивают “почему”?
— Почему? — спросила Ольга, не отрываясь от приставки.
Марта наклонила голову на бок и посмотрела на нее пристальным изучающим взглядом. Потом снова стала красить ногти.
Ее уже не починить. Она же сгорела в прошлом году, даже дымилась, помните? — сказал Марк. — Это бессмысленно.
— Как и вся жизнь. — сказала Ольга.
— Как и вся ты. — сказала Марта. — Чего ты вообще к ней прицепилась? Она _старая_. И вся _грязная_.
Ольга пожала плечами.
— Вдруг захотела снова поиграть. Хорошо было бы снова поиграть, правда?
Ну, наверно. — сказала Марта.
Она поставила лак на стол и быстро потрясла руками в воздухе, чтобы ногти скорее подсохли.
— Так, _я_ готова.
— Мне уже восемнадцать. — сказала Марта. — Я уже здесь самая взрослая.
Внизу громко спорили. Ольга подошла к лестнице и прислушалась. Большую часть криков издавала Марта. Ольга спустилась по лестнице.
— Мне уже шестнадцать! — крикнула Марта. Ей ответили, что просто быть шестнадцатилетней недостаточно.
— Я не хочу ехать! Это тупо!
Она как будто пыталась сдвинуть каменную стену, и та не подавалась ни на миллиметр. Ольга подошла и встала рядом.
— Это дебилизм! — снова крикнула Марта.
Ольга быстро собралась с мыслями и подняла руку:
— Если можно, я бы объяснила…
Ей было позволено объяснить.
Это не дало результата, на который Ольга рассчитывала. Она подумала еще секунду, и сказала:
В этом мире девушки все равно что гусеницы, потом они станут бабочками или мотыльками, но сперва надо превратиться в куколку или там в личинку, у всех по-разному, но судьба общая, так пишет мсье Бюффон в той большой книге, что стоит на полке в соседней зале.
Она обвела взглядом слушателей.
— Мы ответственные гусеницы и намерены стать хорошими бабочками. На два дня вы можете со спокойной душой оставить нас здесь.
Им был предъявлен список условий и требований быть дома не позже десяти часов, не шляться ночью, категорически никакого алкоголя.
Обе с энтузиазмом закивали.
— Я тут сидеть не буду. — заявила Марта и показала на белоснежные брюки. Марк демонстративно вздохнул как сделал бы человек, бесконечно уставший от капризов.
— Пойдём в лодку. — сказал он. — В лодке будешь сидеть?
— Конечно. — сказала Марта. — Лодка же _чистая_.
У костра сидели несколько фигур. Играла музыка, к удивлению, не так уж громко.
— Показываем документы! — сказала она из темноты.
Произошла короткая суета. Музыка была выключена. Что-то было быстро спрятано. Горящая сигарета была выброшена.
— Чего ты так пугаешь! — сказал кто-то, когда волнение уляглось.
— Двигайтесь. — сказала Ольга и села на бревно рядом с кем-то, каким-то юношей, или, может, девушкой. — Что у вас там? Можете не прятать. Бренди?
Не бренди, конечно. — сказал кто-то.
Ну, угощайте, что ли.
Ей передали бутылку. Она глотнула прямо из горлышка. Этикетка выглядела дорогой, но водка была на вкус как отчаяние.
— Только у нас закуски нет. — сказал кто-то.
— Я не закусываю никогда. — ответила Ольга.
Прозвучали одобрительные голоса.
Вкус ржавчины и дешевого лака для ногтей остался во рту даже наутро, когда она обнаружила себя лежащей на траве около дома. Солнце едва поднялось. Все было мокрое от росы и холодное. Каждая клетка ее тела болела.
Потребовалось усилие, чтобы отпереть дверь. В голове кружились какие-то неразборчивые образы: она держит руки на чьих-то плечах и двигается в ритме, напоминающем медленный танец, она пьет из бутылки, кто-то еще пьет из бутылки и нюхает чьи-то волосы. Она остановилась перед зеркалом. На ней все еще была вчерашняя одежда - клетчатая рубашка, уже с пятнами грязи, и драные джинсы. Она смутно вспомнила, как просовывает палец в дырку над коленом и говорит “Рваная, но чистая” и ее вырвало прямо на пол. Она даже не могла вспомнить, кому говорила это.
В конце концов она собрала достаточно сил, чтобы умыться, а потом вышла из дома.
Марту она нашла на речке. Та издалека, из воды замахала ей руками и крикнула что-то. Ольга медленно спустилась на так называемый пляж. Марта подплыла ближе, наполовину вылезла из воды, сделалавшись похожей на горбоносую мускулистую русалку.
Спорим? закричала она. Спорим, я первая доплыву до острова? Давай! Я уже один раз сплавала!
Ольга села на песок и сказала:
Неохота. Мне не в чем.
Ну и что! крикнула Марта. Смотри! Нету же никого!
А там? спросила Ольга.
Там вообще никого! Давай, погнали!
Одежду украдут. сказала Ольга.
Ты что! ответила Марта, и быстро объяснила, что будет с тем, кто украдет одежду.
Да? сказала Ольга. Ну ладно.
Доплыли абсолютно одновременно, но Марта закричала:
Я первая!
Все понятно. ответила Ольга.
Что тебе понятно?
Все мне понятно.
Марта сразу, шумно дыша, бросилась на землю. Ольга из осторожности и из любопытства обошла весь островок, потом еще раз. Раньше она не была здесь, и за все время, проведенное на реке, видела его только издалека. Потом улеглась в стороне от Марты и стала греться на солнце.
Есть хочется. сказала Марта. Сейчас обратно поплывем.
Ольга перевернулась на спину и попробовала смотреть на небо, но было слишком ярко, и приходилось жмуриться. Она снова легла на живот. Голова болела уже гораздо меньше, тошнота почти прошла, и ей даже стало казаться, что впереди еще возможно какое-то будущее, какая-то жизнь. Надо только как-то пережить этот день, но в конце концов люди и не такое переживают. И вот именно в этот момент Марта мечтательно сказала:
Прикинь. Только июнь. Еще целое лето.

View File

@@ -1,7 +1,7 @@
import os, subprocess
import shutil
from pathlib import Path
from classes import DefaultFrontmatterHeader
from classes import *
from config import Config
from logger import logger
@@ -22,7 +22,7 @@ def edit_content(filename):
filename = Path(f"{Config.CONTENT_DIR}/{filename}.md")
subprocess.call([editor, filename])
def init_site():
def init_site_old():
logger.debug(f"Initializing new site")
# Recreate output directory if necessary
@@ -39,7 +39,10 @@ def init_site():
# Create "About.md" content item
create_content("about")
def build_site():
logger.debug(f"Starting build")
def init_site():
logger.debug(f"Initializing new site")
site = SitePrototype
site.init_site()
def build_site():
logger.debug("Building site")

View File

@@ -1,30 +1,9 @@
#!/usr/bin/env python3
from pathlib import Path
import shutil
from config import Config
from logger import logger
from argparser import argparser
from classes import *
from functions import *
content_dir = Path('content')
template_dir = Path('templates')
output_dir = Path(Config.OUTPUT_DIR)
img_dir = Path('public/images')
assets_css_dir = Path(output_dir / 'static/css')
assets_js_dir = Path(output_dir / 'static/js')
static_dir = Path('static')
header_image = Config.HEADER_IMAGE or '/static/header.jpg'
def build_site_old():
logger.info(f"Building the '{Config.MAIN_PAGE_TITLE}' site.")

8
jinja_env.py Normal file
View File

@@ -0,0 +1,8 @@
from jinja2 import Environment, FileSystemLoader
from config import Config
# Prepare template rendering engine
env = Environment(loader=FileSystemLoader(Config.TEMPLATES_DIR))
env.globals['header_image'] = Config.HEADER_IMAGE
index_template = env.get_template("index.html")
content_item_template = env.get_template("content_item.html")

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -0,0 +1,24 @@
{% extends "default.html" %}
{% block head_includes %}
{% if content_item.custom_css %}
<link href="{{ content_item.custom_css }}">
{% endif %}
{% endblock %}
{% block content %}
<div class="container mt-4 mb-5">
{% if content_item.image %}<img src="{{ content_item.image }}" class="img-fluid mb-5" style="border-radius: 5px;">{% endif %}
{% if not content_item.omit_second_title %}
<h1>{{ content_item.title }}</h1>
{% endif %}
<article>{{ content_item.html | safe }}</article>
<a href="index.html" class="btn btn-secondary mt-4">← Back</a>
</div>
{% endblock %}
{% block footer_includes %}
{% if content_item.custom_js %}
<script src="{{ content_item.custom_js }}"></script>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,90 @@
<!doctype html>
<html>
<head>
{% set base = "" %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<title>{{ page_title }}</title>
<style>
.top_header {
position: relative;
height: 250px;
background: rgb(70, 70, 124);
background-image: url("{{ header_image }}");
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.top_header_text {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-weight: bold;
font-size: 2.5rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 5px;
padding: 10px 20px;
border-radius: 10px;
}
.top_menu a {
font-weight: bold;
color: #333;
padding: 6px 12px;
margin-left: 10px;
text-decoration: none;
transition: background 0.2s ease;
}
.top_menu a:hover {
background: rgba(255, 255, 255, 0.3);
}
@media (min-width: 768px) {
.top_header_text {
font-size: 4rem;
}
}
.card-title {
font-weight: bold;
}
article {
font-size: 1.1rem;
line-height: 1.6;
}
{% block head_includes %}
{% endblock %}
</style>
</head>
<body>
<div class="top_header">
<div >
<h1 class="top_header_text"><a style="border-radius: 5px; padding: 10px; opacity: 0.7; background: black">{{ page_title }}</a></h1>
</div>
</div>
<div class="top_menu d-flex justify-content-end" style="margin-bottom: 20px;">
<a href="index.html">Home</a>
<a href="about.html">About</a>
</div>
<div id="content">{% block content %}{% endblock %}</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
{% block footer_includes %}
{% endblock %}
</body>

19
templates.bak/index.html Normal file
View File

@@ -0,0 +1,19 @@
{% extends "default.html" %}
{% block content %}
<div class="row row-cols-1 row-cols-md-3 g-4 px-1 py-2">
{% for content_item in content_items %}
<div class="col mb-3">
<div class="card h-100" style="border-radius: 5px;">
<img src="{{ content_item.image }}" alt="{{ content_item.title }}" class="card-img-top img-fluid" style="background: rgb(77, 77, 77); object-fit: cover; height: 200px; border-radius: 5px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><a class="text-decoration-none text-body" href="{{ content_item.slug}}.html ">{{ content_item.title }}</a></h5>
<p class="card-text mb-3">{{ content_item.preview | safe}}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

140
templates.py Normal file
View File

@@ -0,0 +1,140 @@
default = """
<!doctype html>
<html>
<head>
{% set base = "" %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<title>{{ page_title }}</title>
<style>
.top_header {
position: relative;
height: 250px;
background: rgb(70, 70, 124);
background-image: url("{{ header_image }}");
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.top_header_text {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-weight: bold;
font-size: 2.5rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 5px;
padding: 10px 20px;
border-radius: 10px;
}
.top_menu a {
font-weight: bold;
color: #333;
padding: 6px 12px;
margin-left: 10px;
text-decoration: none;
transition: background 0.2s ease;
}
.top_menu a:hover {
background: rgba(255, 255, 255, 0.3);
}
@media (min-width: 768px) {
.top_header_text {
font-size: 4rem;
}
}
.card-title {
font-weight: bold;
}
article {
font-size: 1.1rem;
line-height: 1.6;
}
{% block head_includes %}
{% endblock %}
</style>
</head>
<body>
<div class="top_header">
<div >
<h1 class="top_header_text"><a style="border-radius: 5px; padding: 10px; opacity: 0.7; background: black">{{ page_title }}</a></h1>
</div>
</div>
<div class="top_menu d-flex justify-content-end" style="margin-bottom: 20px;">
<a href="index.html">Home</a>
<a href="about.html">About</a>
</div>
<div id="content">{% block content %}{% endblock %}</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
{% block footer_includes %}
{% endblock %}
</body>
"""
index = """
{% extends "default.html" %}
{% block content %}
<div class="row row-cols-1 row-cols-md-3 g-4 px-1 py-2">
{% for content_item in content_items %}
<div class="col mb-3">
<div class="card h-100" style="border-radius: 5px;">
<img src="{{ content_item.image }}" alt="{{ content_item.title }}" class="card-img-top img-fluid" style="background: rgb(77, 77, 77); object-fit: cover; height: 200px; border-radius: 5px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><a class="text-decoration-none text-body" href="{{ content_item.slug}}.html ">{{ content_item.title }}</a></h5>
<p class="card-text mb-3">{{ content_item.preview | safe}}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
"""
content_item="""
{% extends "default.html" %}
{% block head_includes %}
{% if content_item.custom_css %}
<link href="{{ content_item.custom_css }}">
{% endif %}
{% endblock %}
{% block content %}
<div class="container mt-4 mb-5">
{% if content_item.image %}<img src="{{ content_item.image }}" class="img-fluid mb-5" style="border-radius: 5px;">{% endif %}
{% if not content_item.omit_second_title %}
<h1>{{ content_item.title }}</h1>
{% endif %}
<article>{{ content_item.html | safe }}</article>
<a href="index.html" class="btn btn-secondary mt-4">← Back</a>
</div>
{% endblock %}
{% block footer_includes %}
{% if content_item.custom_js %}
<script src="{{ content_item.custom_js }}"></script>
{% endif %}
{% endblock %}
"""

View File

@@ -1,3 +1,4 @@
{% extends "default.html" %}
{% block head_includes %}
@@ -21,4 +22,4 @@
{% if content_item.custom_js %}
<script src="{{ content_item.custom_js }}"></script>
{% endif %}
{% endblock %}
{% endblock %}

View File

@@ -87,4 +87,4 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
{% block footer_includes %}
{% endblock %}
</body>
</body>

View File

@@ -1,3 +1,4 @@
{% extends "default.html" %}
{% block content %}
@@ -16,4 +17,4 @@
{% endfor %}
</div>
{% endblock %}
{% endblock %}