6 Commits

Author SHA1 Message Date
Sweetbread 20eac7c89e Update footer
Docker Build and Push / build-and-push (push) Successful in 34s
2026-01-18 01:29:17 +03:00
Sweetbread 7ed54a7e51 Remove private sites 2026-01-18 01:26:32 +03:00
Sweetbread e564a24ad4 wip: update my page 2026-01-18 01:25:10 +03:00
Sweetbread 335eaa0545 Move locale things into module 2026-01-17 23:51:32 +03:00
Sweetbread 4b2720f8b7 Put personal pages into subdomains 2026-01-17 23:51:32 +03:00
Sweetbread aedd47ba36 Change (temporary?) icon 2026-01-17 23:51:32 +03:00
49 changed files with 418 additions and 220 deletions
+2 -2
View File
@@ -5,5 +5,5 @@ __pycache__/
*$py.class
.python-version
static/style/*.css
static/style/*.css.map
*.css
*.css.map
+3 -3
View File
@@ -2,8 +2,8 @@ FROM node:18-alpine as sass
RUN NODE_OPTIONS=--dns-result-order=ipv4first npm install -g sass
WORKDIR /build
COPY ./static/style ./style
RUN sass ./style:./style \
COPY ./blueprints ./blueprints
RUN sass ./blueprints:./blueprints \
--no-source-map \
--style=compressed
@@ -13,7 +13,7 @@ FROM python:3.11-slim
WORKDIR /app
COPY . .
COPY --from=sass /build/style/ ./static/style/
COPY --from=sass /build/blueprints/ ./blueprints/
RUN pip install --no-cache-dir -r requirements.txt
+19 -71
View File
@@ -1,79 +1,27 @@
from os import system as console
from configparser import ConfigParser
from htmlmin import minify
from flask import (
Flask,
g,
request,
render_template,
)
from modules import locale
from blueprints.root import bp as root_bp
from blueprints.risdeveau import bp as rdv_bp
import blueprints.root.modules.style
import blueprints.risdeveau.modules.style
from flask import Flask
translations_cache = {}
app = Flask(__name__, static_folder=None, subdomain_matching=True)
def load_translations(lang):
if lang not in translations_cache:
translations_cache[lang] = {}
app.before_request(locale.before_request)
app.context_processor(locale.inject_translations)
try:
config = ConfigParser()
config.read(f'locale/{lang}.ini')
for section in config.sections():
translations_cache[lang][section] = dict(config.items(section))
except:
pass
return translations_cache[lang]
def get_locale():
return request.accept_languages.best_match(('en', 'ru', 'de', 'fr', 'ja'), 'en')
app = Flask(__name__)
@app.before_request
def before_request():
g.locale = get_locale()
g.translations = load_translations(g.locale)
@app.context_processor
def inject_translations():
def translate(text, **kwargs):
if ":" in text:
section, key = text.split(":", 1)
else:
section, key = "common", text
template = g.translations \
.get(section, {}) \
.get(key, f"${section}: {key}$")
try:
return template.format(**kwargs)
except:
return template
return {'_': translate}
app.register_blueprint(root_bp)
app.register_blueprint(rdv_bp)
if app.debug:
console("sass static/style/main.scss static/style/main.css")
console("sass static/style/risdeveau.scss static/style/risdeveau.css")
blueprints.root.modules.style.compile_styles()
blueprints.risdeveau.modules.style.compile_styles()
@app.route("/")
def index():
return minify(render_template('index.html'), remove_empty_space=True)
@app.route("/host")
def host():
return minify(render_template('host.html'), remove_empty_space=True)
@app.route("/us")
def us():
return minify(render_template('us.html'), remove_empty_space=True)
@app.route("/risdeveau")
def risdeveau():
return minify(render_template('personal/risdeveau.html'), remove_empty_space=True)
app.config['SERVER_NAME'] = "localhost:5000"
else:
app.config['SERVER_NAME'] = "lair.moe"
+39
View File
@@ -0,0 +1,39 @@
import os
from pathlib import Path
from htmlmin import minify
from flask import Blueprint, render_template, send_from_directory, send_file, abort
bp = Blueprint(
"risdeveau",
__name__,
subdomain="risdeveau",
template_folder="..",
static_folder=None
)
def render_tmpl(filename: str) -> str:
template_path = os.path.join("risdeveau/templates", filename)
return minify(
render_template(template_path),
remove_empty_space=True
)
@bp.route("/static/<path:filename>")
def static(filename: str):
static_folders = ("static", "../root/static")
for dir in static_folders:
if os.path.exists(path := os.path.join("blueprints/risdeveau", dir, filename)):
return send_file(path)
return abort(404)
@bp.route("/")
def index():
return render_tmpl('index.html')
@bp.route("/contacts")
def contacts():
return render_tmpl('contacts.html')
@bp.route("/donate")
def donate():
return render_tmpl('donate.html')
+9
View File
@@ -0,0 +1,9 @@
from os import system as console
from os.path import join
def compile_styles():
dir = "blueprints/risdeveau/static/style"
files = ("risdeveau",)
for file in files:
console(f"sass {join(dir, file+'.scss')} {join(dir, file+'.css')}")

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,80 @@
@use "sass:color";
// Palette: Catppuccin Mocha
// https://catppuccin.com/palette/
$base: #1e1e2e;
$text: #cdd6f4;
$mantle: #181825;
$crust: #11111b;
$overlay0: #6c7086;
$overlay1: #7f849c;
$overlay2: #9399b2;
$surface0: #313244;
$surface1: #45475a;
$surface2: #585b70;
$subtext0: #a6adc8;
$subtext1: #bac2de;
$red: #f38ba8;
$green: #a6e3a1;
$peach: #fab387;
$blue: #89b4fa;
$mauve: #8839ef;
h3 {
margin-block-end: 0;
}
.qr {
img { width: 100% }
p { text-align: center; }
&.blocks {
flex-wrap: nowrap;
overflow-x: auto;
width: calc(100vw - 1rem);
max-width: 45rem;
scroll: {
behavior: smooth;
snap-type: x mandatory;
}
&::-webkit-scrollbar { display: none; }
&:hover .block.qr:not(:hover) {
filter: blur(5px);
transition: all 0.3s ease;
}
}
&.block {
flex: 0 0 calc(100vw - 2rem);
scroll-snap-align: start;
max-width: 13.666rem;
}
}
table, tbody {
vertical-align: baseline;
border-collapse: collapse;
tr {
border-radius: 10px;
&:hover {
background-color: color.change($surface1, $alpha:75%);
}
th {
text-align: unset;
}
th + td, td + td {
padding-left: 2rem;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Sweet Bread</title>
<link rel="stylesheet" href="/static/style/main.css">
<link rel="stylesheet" href="/static/style/risdeveau.css">
<link rel="icon" type="image/webp" href="/static/icon/us/risdeveau.webp" />
<script
src="https://track.lair.moe/api/script.js"
data-site-id="1"
defer
></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
{% include 'risdeveau/templates/header.tmpl' %}
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
@@ -0,0 +1,50 @@
{% extends 'risdeveau/templates/base.tmpl' %}
{% block content %}
<h3>Development</h3>
<div class="blocks badges">
<a class="block" href="//g.lair.moe/Sweetbread">
<img class="icon" src="/static/icon/service/gitea.webp" />
Gitea
</a>
<a class="block" href="https://github.com/VerySweetBread">
<img class="icon" src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" />
GitHub
</a>
<a class="block" href="https://git.kolibrios.org/Sweetbread">
<img class="icon" src="https://git.kolibrios.org/assets/img/logo.svg" />
KolibriOS Git
</a>
</div>
<h3>Contacts</h3>
<div class="blocks badges">
<a class="block" href="https://matrix.to/#/@risdeveau:codrs.ru">
<img class="icon" src="https://matrix.org/assets/favicon.ico" />
Matrix
</a>
<a class="block" href="//b.lair.moe/@risdeveau">
<img class="icon" src="/static/icon/service/sharkey.webp" />
Fediverse
</a>
<a class="block" href="https://discord.com/users/459823895256498186">
<img class="icon" src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/66e3d80db9971f10a9757c99_Symbol.svg" />
Discord
</a>
<a class="block" href="mailto:risdeveau@lair.moe">
Mail
</a>
</div>
<h3>Game accounts</h3>
<div class="blocks badges">
<a class="block" href="https://steamcommunity.com/id/risdeveau">
<img class="icon" src="https://store.steampowered.com/favicon.ico" />
Steam
</a>
<a class="block" href="https://gamebanana.com/members/3899828">
<img class="icon" src="https://images.gamebanana.com/static/img/favicon/favicon.ico" />
GameBanana
</a>
</div>
{% endblock %}
@@ -0,0 +1,21 @@
{% extends 'risdeveau/templates/base.tmpl' %}
{% block content %}
<h3>Wallets</h3>
<div class="blocks qr">
<div class="block qr">
<p>POL, BNB</p>
<img src="/static/img/wallets/evm.webp">
</div>
<div class="block qr">
<p>TON</p>
<img src="/static/img/wallets/ton.webp">
</div>
<div class="block qr">
<p>XMR</p>
<img src="/static/img/wallets/xmr.webp">
</div>
</div>
{% endblock %}
@@ -0,0 +1,20 @@
<header>
{%- if request.path != url_for('.index') %}
<a href="{{ url_for('.index') }}">Main</a>
{%- else %}
<a href="{{ url_for('root.index') }}">Lair</a>
{%- endif %}
<div class="header-links">
{%- for (l, t) in (
('.contacts', _('contacts')),
('.donate', _('donate'))
) %}
{%- if url_for(l) == request.path %}
<strong>{{ t }}</strong>
{%- else %}
<a href="{{ url_for(l) }}">{{ t }}</a>
{%- endif %}
{%- endfor %}
</div>
</header>
+54
View File
@@ -0,0 +1,54 @@
{% extends 'risdeveau/templates/base.tmpl' %}
{% block content %}
<div class="block">
<table>
<tr>
<th>DoB</th>
<td>2005-01-13</td>
</tr>
<tr>
<th>Languages</th>
<td>
<table>
<tr>
<td>Russian</td>
<td>Native</td>
</tr>
<tr>
<td>English</td>
<td>B2</td>
</tr>
<tr>
<td>French</td>
<td>A1?</td>
</tr>
<tr>
<td>German</td>
<td>A2?</td>
</tr>
<tr>
<td>Japanese</td>
<td>Beginner</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>Student</th>
<td>
<table>
<tr>
<td>Programmer</td>
<td>2/4yr.</td>
</tr>
<tr>
<td>Translator</td>
<td>2/3yr.</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
{% endblock %}
+28
View File
@@ -0,0 +1,28 @@
from htmlmin import minify
from flask import Blueprint, render_template, request, jsonify
bp = Blueprint(
"root",
__name__,
template_folder="templates",
static_folder="static"
)
def render_tmpl(filename: str) -> str:
return minify(
render_template(filename),
remove_empty_space=True
)
@bp.route("/")
def index():
return render_tmpl('index.html')
@bp.route("/host")
def host():
return render_tmpl('host.html')
@bp.route("/us")
def us():
return render_tmpl('us.html')
+9
View File
@@ -0,0 +1,9 @@
from os import system as console
from os.path import join
def compile_styles():
dir = "blueprints/root/static/style"
files = ("main",)
for file in files:
console(f"sass {join(dir, file+'.scss')} {join(dir, file+'.css')}")
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

@@ -4,7 +4,7 @@
<title>Lair</title>
<link rel="stylesheet" href="/static/style/main.css">
<link rel="icon" type="image/webp" href="/static/icon/codrs.webp" />
<link rel="icon" type="image/webp" href="/static/icon/lair.webp" />
<script src="/static/script/copy-mono.js"> </script>
<script
src="https://track.lair.moe/api/script.js"
@@ -1,5 +1,5 @@
<footer>
<div>lair.moe &#169; 2025</div>
<div>lair.moe &#127279; 2025 - 2026</div>
<div><a href="https://g.lair.moe/Sweetbread/lair.moe">{{ _('site source') }}</a></div>
<div>{{ _('contact us') }}: <a href="mailto:admin@lair.moe">admin@lair.moe</a></div>
</footer>
@@ -1,14 +1,14 @@
<header>
{%- if request.path != url_for('index') %}
<a href="{{ url_for('index') }}">Lair</a>
{%- if request.path != url_for('.index') %}
<a href="{{ url_for('.index') }}">Lair</a>
{%- else %}
<div></div>
{%- endif %}
<div class="header-links">
{%- for (l, t) in (
('us', _('about us')),
('host', _('about host'))
('.us', _('about us')),
('.host', _('about host'))
) %}
{%- if url_for(l) == request.path %}
<strong>{{ t }}</strong>
@@ -1,7 +1,7 @@
{% extends 'base.tmpl' %}
{% block title %}
<img src="/static/icon/codrs.webp" class="icon" />
<img src="/static/icon/lair.webp" class="icon" />
Lair
{% endblock %}
@@ -49,14 +49,4 @@
<span class="mono">200:ee1:bad2:1732:4b91:c3e3:2f08:29b3</span>
</p>
</div>
<div class="block">
{{
_('index:bottom_text',
glitchtip='<a href="https://bug.codrs.ru" target="_blank"><strong>GlitchTip</strong></a>',
baikal='<a href="https://dav.lair.moe" target="_blank"><strong>Baikal</strong></a>',
freshrss='<a href="https://rss.lair.moe" target="_blank"><strong>FreshRSS</strong></a>',
) | safe
}}
</div>
{% endblock %}
@@ -3,7 +3,7 @@
{% block title %}О нас{% endblock %}
{% block content %}
<a href="{{ url_for('risdeveau') }}" class="block green">
<a href="{{ url_for('risdeveau.index') }}" class="block green">
<div class="header">
<img src="/static/icon/us/risdeveau.webp" class="icon"/>
Sweetbread
-1
View File
@@ -8,7 +8,6 @@ about host = Über Server
[index]
altfronts = Altfronts
bottom_text = Außerdem bieten wir {glitchtip}, {baikal} und {freshrss} für Mitglieder unserer Gruppe an!
[index.descr]
sharkey = Föderierter Microblogging-Dienst auf Basis des ActivityPub-Protokolls
+3 -1
View File
@@ -5,10 +5,12 @@ contact us = Contact us
about us = About us
about host = About host
contacts = Contacts
donate = Donate
[index]
altfronts = Altfronts
bottom_text = We also have {glitchtip}, {baikal} and {freshrss} for members of our squad!
[index.descr]
sharkey = ActivityPub-based federated microblogging service
-1
View File
@@ -8,7 +8,6 @@ about host = À propos de serveur
[index]
altfronts = Altfronts
bottom_text = On a aussi {glitchtip}, {baikal} et {freshrss} pour les membres du groupe !
[index.descr]
sharkey = Service de microblogging fédéré avec ActivityPub
-1
View File
@@ -8,7 +8,6 @@ about host = サーバーについて
[index]
altfronts = 代替フロントエンド
bottom_text = メンバーには {glitchtip}、{baikal}、{freshrss} も使えるよ!
[index.descr]
sharkey = ActivityPubを使った連合型マイクロブログ
+3 -1
View File
@@ -5,10 +5,12 @@ contact us = Для связи
about us = О нас
about host = О хосте
contacts = Контакты
donate = Донат
[index]
altfronts = Альтфронты
bottom_text = Ещё у нас есть {glitchtip}, {baikal} и {freshrss} для участников нашей группы!
[index.descr]
sharkey = Федеративная микроблогинговая система поверх протокола ActivityPub
+47
View File
@@ -0,0 +1,47 @@
from configparser import ConfigParser
from flask import g, request
translations_cache = {}
def load_translations(lang):
if lang not in translations_cache:
translations_cache[lang] = {}
try:
config = ConfigParser()
config.read(f'locale/{lang}.ini')
for section in config.sections():
translations_cache[lang][section] = dict(config.items(section))
except:
pass
return translations_cache[lang]
def get_locale():
return request.accept_languages.best_match(
('en', 'ru', 'de', 'fr', 'ja'),
) or 'en'
def before_request():
g.locale = get_locale()
g.translations = load_translations(g.locale)
def inject_translations():
def translate(text, **kwargs):
if ":" in text:
section, key = text.split(":", 1)
else:
section, key = "common", text
template = g.translations \
.get(section, {}) \
.get(key, f"${section}: {key}$")
try:
return template.format(**kwargs)
except:
return template
return {'_': translate}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

-33
View File
@@ -1,33 +0,0 @@
h3 {
margin-block-end: 0;
}
.qr {
img { width: 100% }
p { text-align: center; }
&.blocks {
flex-wrap: nowrap;
overflow-x: auto;
width: calc(100vw - 1rem);
max-width: 45rem;
scroll: {
behavior: smooth;
snap-type: x mandatory;
}
&::-webkit-scrollbar { display: none; }
&:hover .block.qr:not(:hover) {
filter: blur(5px);
transition: all 0.3s ease;
}
}
&.block {
flex: 0 0 calc(100vw - 2rem);
scroll-snap-align: start;
max-width: 13.666rem;
}
}
-88
View File
@@ -1,88 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Sweet Bread</title>
<link rel="stylesheet" href="/static/style/main.css">
<link rel="stylesheet" href="/static/style/risdeveau.css">
<link rel="icon" type="image/webp" href="/static/icon/us/risdeveau.webp" />
<script
src="https://track.lair.moe/api/script.js"
data-site-id="1"
defer
></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header>
<a href="{{ url_for('index') }}">Lair</a>
</header>
<main>
<h3>Development</h3>
<div class="blocks badges">
<a class="block" href="//g.lair.moe/Sweetbread">
<img class="icon" src="/static/icon/service/gitea.webp" />
Gitea
</a>
<a class="block" href="https://github.com/VerySweetBread">
<img class="icon" src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" />
GitHub
</a>
<a class="block" href="https://git.kolibrios.org/Sweetbread">
<img class="icon" src="https://git.kolibrios.org/assets/img/logo.svg" />
KolibriOS Git
</a>
</div>
<h3>Contacts</h3>
<div class="blocks badges">
<a class="block" href="https://matrix.to/#/@risdeveau:codrs.ru">
<img class="icon" src="https://matrix.org/assets/favicon.ico" />
Matrix
</a>
<a class="block" href="//b.lair.moe/@risdeveau">
<img class="icon" src="/static/icon/service/sharkey.webp" />
Fediverse
</a>
<a class="block" href="https://discord.com/users/459823895256498186">
<img class="icon" src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/66e3d80db9971f10a9757c99_Symbol.svg" />
Discord
</a>
<a class="block" href="mailto:risdeveau@lair.moe">
Mail
</a>
</div>
<h3>Game accounts</h3>
<div class="blocks badges">
<a class="block" href="https://steamcommunity.com/id/risdeveau">
<img class="icon" src="https://store.steampowered.com/favicon.ico" />
Steam
</a>
<a class="block" href="https://gamebanana.com/members/3899828">
<img class="icon" src="https://images.gamebanana.com/static/img/favicon/favicon.ico" />
GameBanana
</a>
</div>
<h3>Wallets</h3>
<div class="blocks qr">
<div class="block qr">
<p>POL, BNB</p>
<img src="/static/img/risdeveau/wallets/evm.webp">
</div>
<div class="block qr">
<p>TON</p>
<img src="/static/img/risdeveau/wallets/ton.webp">
</div>
<div class="block qr">
<p>XMR</p>
<img src="/static/img/risdeveau/wallets/xmr.webp">
</div>
</div>
</main>
</body>
</html>