Compare commits

..

15 Commits

Author SHA1 Message Date
6360b41e9a fkin finally 2025-08-21 23:23:14 +01:00
217adf91e9 templating 2025-08-21 21:15:21 +01:00
a7404fed3d crow setup 2025-08-20 23:08:54 +01:00
bfb1b8a21e sitemap
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 3m59s
2025-08-13 22:54:24 +01:00
ef459d728a update link
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 3m16s
2025-08-10 19:53:31 +01:00
5690bcadf9 I fkin found em
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 4m21s
2025-08-08 20:31:55 +01:00
8b5b80f7c5 Merge remote-tracking branch 'refs/remotes/gitea/main'
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 3m7s
2025-08-07 21:26:35 +01:00
66806ad922 events 2025-08-07 21:25:10 +01:00
b29a61a44b fix name box
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 3m2s
2025-08-07 11:28:24 +00:00
a0562330a3 more updates
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 2m59s
2025-08-06 22:39:06 +01:00
df59a2c097 fix typos and css
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 2m31s
2025-08-06 01:33:56 +01:00
8209f52fa7 update og image
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 2m19s
2025-08-06 01:15:44 +01:00
b029eba456 fix
All checks were successful
Build and push container image / build-and-push-image (push) Successful in 2m27s
2025-08-06 00:54:30 +01:00
1b2425a493 toaster update :3
Some checks failed
Build and push container image / build-and-push-image (push) Failing after 5m28s
2025-08-06 00:41:26 +01:00
e66f7e0588 update 2025-06-28 18:12:09 +01:00
62 changed files with 604 additions and 6785 deletions

View File

@@ -0,0 +1,33 @@
name: Build and push container image
run-name: ${{ gitea.actor }} is building and pushing container image
on:
push:
branches:
- main
env:
GITEA_DOMAIN: git.alfieking.dev
GITEA_REGISTRY_USER: acetheking987
RESULT_IMAGE_NAME: acetheking987/alfieking.dev
jobs:
build-and-push-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITEA_DOMAIN }}
username: ${{ env.GITEA_REGISTRY_USER }}
password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ env.GITEA_DOMAIN }}/${{ env.RESULT_IMAGE_NAME }}:latest

7
.gitignore vendored
View File

@@ -1,5 +1,2 @@
.venv build/
.env .vscode/
db.sqlite
flask_session
__pycache__

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "thirdparty/inja"]
path = thirdparty/inja
url = https://github.com/pantor/inja
[submodule "thirdparty/nlohmann"]
path = thirdparty/nlohmann
url = https://github.com/nlohmann/json

6061
app.log

File diff suppressed because it is too large Load Diff

BIN
db.sqlite

Binary file not shown.

View File

@@ -1,25 +0,0 @@
FROM python:alpine
# Set the working directory
WORKDIR /app
# Copy the requirements file into the container
COPY requirements.txt .
# Install the required packages
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn
# Copy the rest of the application code into the container
COPY src src
COPY templates templates
COPY static static
# Expose the port the app runs on
EXPOSE 5000
# Set environment variables
ENV FLASK_APP=app.py
# run the application
ENTRYPOINT [ "gunicorn", "-b", ":5000", "--access-logfile", "-", "--error-logfile", "-", "src.wsgi:app" ]

15
include/errors.hpp Normal file
View File

@@ -0,0 +1,15 @@
#ifndef ERRORS_HPP
#define ERRORS_HPP
#include <crow.h>
#include "templating.hpp"
extern Templating templating;
struct CustomErrorHandler {
struct context {};
void before_handle(crow::request& req, crow::response& res, context&) {}
void after_handle(crow::request& req, crow::response& res, context&);
};
#endif

25
include/templating.hpp Normal file
View File

@@ -0,0 +1,25 @@
#ifndef TEMPLATING_HPP
#define TEMPLATING_HPP
#include <inja/inja.hpp>
#include <crow.h>
#include <string>
class Templating {
public:
explicit Templating(const std::string& template_dir);
crow::response render_template(const std::string& template_name, const inja::json& data);
crow::response render_template(const std::string& template_name);
std::string render_template_string(const std::string& template_name, const inja::json& data);
std::string render_template_string(const std::string& template_name);
private:
inja::Environment inja_env;
std::string template_dir; // absolute path to templates
std::string preprocess_template(const std::string& template_name);
};
#endif

51
makefile Normal file
View File

@@ -0,0 +1,51 @@
# Directories
INCLUDE_DIRS = include thirdparty/inja/include thirdparty/nlohmann/include
SRC_DIR = src
BUILD_DIR = build
# Compiler and linker settings
CXX = g++
LIBS =
CXXFLAGS = -std=c++17 $(foreach dir,$(INCLUDE_DIRS),-I$(dir))
# Source and object files
SRC = $(wildcard $(SRC_DIR)/*.cpp)
# Target executable
UNAME := $(shell uname -s)
BUILD_DIR := $(BUILD_DIR)/$(UNAME)
OBJ_DIR := $(BUILD_DIR)/objs
BIN = $(BUILD_DIR)/main
# Object files corresponding to the source files (now in obj directory)
OBJS = $(addprefix $(OBJ_DIR)/, $(addsuffix .o, $(basename $(notdir $(SRC)))))
# development target with debugging
dev: CXXFLAGS += -g -Wall -Wformat
dev: all
# Release target
release: CXXFLAGS += -O3
release: all
# Create directories for build output
dirs:
@mkdir -p $(BUILD_DIR)
@mkdir -p $(OBJ_DIR)
# Clear build directory
clear:
@find $(OBJ_DIR) -type f -name '*.o' -exec rm -f {} +
# Pattern rule for source files in src directory
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $<
all: dirs clear $(BIN)
@echo Build complete
$(BIN): $(OBJS)
$(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS)
clean:
rm -rf $(BUILD_DIR)

View File

@@ -1,4 +0,0 @@
python-dotenv
flask-session
requests
flask

5
run.sh
View File

@@ -1,5 +0,0 @@
#!/bin/bash
[ ! -f .env ] || export $(grep -v '^#' .env | xargs)
flask --app src.wsgi --debug run

13
src/errors.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "errors.hpp"
std::string render_404_template(const crow::request& req) {
return templating.render_template_string("errors/404.html", {{"requested_url", req.url}});
}
void CustomErrorHandler::after_handle(crow::request& req, crow::response& res, context&) {
if (res.code == 404 && res.body.empty()) {
res.set_header("Content-Type", "text/html");
res.body = render_404_template(req);
}
}

18
src/main.cpp Normal file
View File

@@ -0,0 +1,18 @@
#define CROW_STATIC_DIRECTORY "../static"
#include "templating.hpp"
#include "errors.hpp"
#include <crow.h>
Templating templating{"../templates"};
int main() {
crow::App<CustomErrorHandler> app;
CROW_ROUTE(app, "/")([]() {
return templating.render_template("index.html");
});
app.port(8080).multithreaded().run();
}

View File

@@ -1,48 +0,0 @@
# Imports
from flask import Blueprint, render_template
from os import getenv as env
import logging
import src.routes.snake as snake
# Create blueprint
bp = Blueprint(
'error_handlers',
__name__,
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
static_folder=env('STATIC_FOLDER', default='../static')
)
# Create logger
log = logging.getLogger(__name__)
# Route for 500 error
@bp.route('/500')
@bp.app_errorhandler(500)
def internal_server_error(error=None):
if error is not None:
log.error("Internal server error: %s", error)
return render_template('errors/500.html'), 500
# Route for 404 error
@bp.route('/404')
@bp.app_errorhandler(404)
def not_found(error=None):
if error is not None:
log.warning("Page not found: %s", error)
scores = snake.get_leaderboard()
token = snake.generate_start_token()
return render_template('errors/404.html', scores=scores, token=token), 404 if error is not None else 200
# Route for 400 error
@bp.route('/400')
@bp.app_errorhandler(400)
def bad_request(error=None):
if error is not None:
log.warning("Bad request: %s", error)
return render_template('errors/400.html', error=error), 400

View File

@@ -1,44 +0,0 @@
# Imports
from flask import Blueprint, render_template, request, abort, send_from_directory
from os import getenv as env
import logging
# Create blueprint
bp = Blueprint(
'generic',
__name__,
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
static_folder=env('STATIC_FOLDER', default='../static')
)
# Create logger
log = logging.getLogger(__name__)
# Route for index page
@bp.route('/')
def index():
return render_template('index.html')
# Route for robots.txt, sitemap.xml, and favicon.ico
@bp.route('/robots.txt')
@bp.route('/sitemap.xml')
@bp.route('/favicon.ico')
def web_stuffs():
return send_from_directory(
env('STATIC_FOLDER', default='../static'),
request.path.lstrip('/')
)
# catch-all route for any other static pages (only in root path)
@bp.route('/<string:filename>')
def static_files(filename):
try:
return render_template(filename if filename.endswith('.html') else filename + '.html')
except Exception as e:
log.error(f"Error serving static file {filename}: {e}")
abort(404)

View File

@@ -1,146 +0,0 @@
# Imports
from flask import Blueprint, abort, request, redirect
from os import urandom, getenv as env
import src.utils.database as database
import src.utils.cap as cap
import logging, datetime, threading, time
# Create blueprint
bp = Blueprint(
'snake',
__name__,
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
static_folder=env('STATIC_FOLDER', default='../static')
)
# Create logger
log = logging.getLogger(__name__)
# Create database instance
db = database.Database(db_name=env('DB_NAME', default='db.sqlite'))
db.execute('CREATE TABLE IF NOT EXISTS snake_scores (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score INTEGER)')
db.execute('''CREATE TABLE IF NOT EXISTS snake_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip TEXT UNIQUE NOT NULL
)''')
# Input validation function
def valid_length(value, min_length=1, max_length=100):
if not isinstance(value, str):
return False
return min_length <= len(value) <= max_length
def valid_score(score, game_token):
start_time = db.execute('SELECT created_at FROM snake_tokens WHERE token = ?', (game_token,)).fetchone()
if not start_time:
log.error("Game token not found.")
return False
start_time = datetime.datetime.fromisoformat(start_time[0])
current_time = datetime.datetime.now()
elapsed_time = (current_time - start_time).total_seconds()
if elapsed_time < score / 10 * 5 + 15: # assuming that each point takes 3 seconds to achieve and 15 seconds to start the game and do captcha
log.error("Score is too high for the elapsed time.")
return False
if score <= 0 or score > 10000: # Arbitrary upper limit for scores
log.error("Score is out of valid range.")
return False
if score % 10 != 0:
log.error("Score is not a multiple of 10.")
return False
# delete the token after score validation
db.execute('DELETE FROM snake_tokens WHERE token = ?', (game_token,))
log.info(f"Score {score} validated successfully for token {game_token}.")
return True
# Route for score submission
@bp.route('/snake/submit', methods=['POST'])
def submit_score():
name = request.form.get('name')
score = request.form.get('score')
captcha_token = request.form.get('cap-token')
game_token = request.form.get('game_token')
if not cap.verify_captcha(captcha_token):
log.error("Captcha verification failed.")
abort(400, "Captcha verification failed")
if not name or not score or not captcha_token or not game_token:
log.error("Name, score, captcha token, or game token is missing.")
abort(400, "Missing required fields")
if not valid_length(name, min_length=3, max_length=15):
log.error("Invalid name length.")
abort(400, "Name must be between 3 and 15 characters long.")
if not valid_score(int(score), game_token):
log.error("Invalid score.")
abort(400, "Score not vilid, so either you are trying to cheat the leaderboard or something is seriously wrong.")
try:
db.execute('INSERT INTO snake_scores (name, score) VALUES (?, ?)', (name, int(score)))
db.execute('DELETE FROM snake_tokens WHERE token = ?', (game_token,))
log.info(f"Score submitted: {name} - {score}")
return redirect('/404')
except Exception as e:
log.error(f"Database error: {e}")
abort(500, "Internal server error while submitting score.")
# Generate a unique game token
def generate_start_token():
"""Generate a unique start token for the game."""
token = urandom(16).hex()
ip = request.headers.get('X-Forwarded-For', request.remote_addr)
ip_token = db.execute('SELECT token FROM snake_tokens WHERE ip = ?', (ip,)).fetchone()
if ip_token:
log.info(f"Token already exists for IP: {ip}, reusing token.")
return ip_token[0]
log.info(f"Generated start token: {token}")
db.execute('INSERT INTO snake_tokens (token, ip) VALUES (?, ?)', (token, ip))
return token
# Get leaderboard scores
def get_leaderboard():
"""Fetch scores from the leaderboard."""
try:
scores = db.execute('SELECT name, score FROM snake_scores ORDER BY score DESC').fetchall()
leaderboard = [{'position': i + 1, 'name': score[0], 'score': score[1]} for i, score in enumerate(scores)]
log.info("Leaderboard fetched successfully.")
return leaderboard
except Exception as e:
log.error(f"Error fetching leaderboard: {e}")
return []
# Clear all tokens older than 1 hour
def clear_old_tokens():
while True:
try:
one_hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)
db.execute('DELETE FROM snake_tokens WHERE created_at < ?', (one_hour_ago,))
log.info("Old tokens cleared.")
except Exception as e:
log.error(f"Error clearing old tokens: {e}")
time.sleep(3600) # Run every hour
# Start the token clearing thread
token_thread = threading.Thread(target=clear_old_tokens, daemon=True)
token_thread.start()

76
src/templating.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "templating.hpp"
#include <filesystem>
#include <fstream>
#include <regex>
#include <stdexcept>
Templating::Templating(const std::string& template_dir)
: inja_env(std::filesystem::canonical(template_dir).string()),
template_dir(std::filesystem::canonical(template_dir).string())
{
inja_env.set_search_included_templates_in_files(true);
}
std::string Templating::preprocess_template(const std::string& template_name) {
namespace fs = std::filesystem;
fs::path abs_template_dir = fs::path(template_dir);
fs::path abs_template_file = fs::canonical(abs_template_dir / template_name);
std::ifstream file(abs_template_file);
if (!file.is_open()) {
throw std::runtime_error("Failed to open template file: " + abs_template_file.string());
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
std::regex extends_regex(R"(\{\%\s*extends\s*(['"])(.+?)\1\s*\%\})");
std::smatch match;
if (std::regex_search(content, match, extends_regex)) {
std::string quote = match[1].str();
std::string original_path = match[2].str();
if (original_path.find("/") == std::string::npos &&
!original_path.empty() &&
original_path.front() != '/') {
fs::path abs_extended_template = fs::canonical(abs_template_dir / original_path);
fs::path rel_path = fs::relative(abs_extended_template, abs_template_file.parent_path());
std::string new_path = rel_path.generic_string();
content = std::regex_replace(content, extends_regex,
"{% extends " + quote + new_path + quote + " %}");
}
}
return content;
}
crow::response Templating::render_template(const std::string& template_name, const inja::json& data) {
try {
std::string preprocessed = preprocess_template(template_name);
inja::Template tpl = inja_env.parse(preprocessed);
std::string rendered = inja_env.render(tpl, data);
return crow::response(rendered);
} catch (const std::exception& e) {
return crow::response(500, e.what());
}
}
crow::response Templating::render_template(const std::string& template_name) {
return render_template(template_name, inja::json{});
}
std::string Templating::render_template_string(const std::string& template_name, const inja::json& data) {
std::string preprocessed = preprocess_template(template_name);
inja::Template tpl = inja_env.parse(preprocessed);
return inja_env.render(tpl, data);
}
std::string Templating::render_template_string(const std::string& template_name) {
return render_template_string(template_name, inja::json{});
}

View File

@@ -1,42 +0,0 @@
# Imports
from os import getenv as env
import requests, logging
# Create logger
log = logging.getLogger(__name__)
# Function to verify CAPTCHA response
def verify_captcha(token: str) -> bool:
"""
Verify the CAP response token with the CAP server.
Args:
token (str): The CAP response token to verify.
Returns:
bool: True if the token is valid, False otherwise.
"""
if not token:
return False
try:
response = requests.post(
env('CAP_VERIFY_URL', default='https://<instance_url>/<key_id>/siteverify'),
json={
'secret': env('CAP_SECRET', default=''),
'response': token,
},
timeout=10
)
response.raise_for_status()
if response.status_code != 200:
log.error("CAPTCHA verification failed with status code: %s", response.status_code)
return False
return response.json().get('success', False)
except Exception as e:
log.error("Error verifying CAPTCHA: %s", e)
return False

View File

@@ -1,18 +0,0 @@
# Imports
import sqlite3
# Database class
class Database:
def __init__(self, db_name='db.sqlite'):
self.connection = sqlite3.connect(db_name, check_same_thread=False)
self.cursor = self.connection.cursor()
def execute(self, query, params=None):
if params is None:
params = []
self.cursor.execute(query, params)
self.connection.commit()
return self.cursor
def close(self):
self.connection.close()

View File

@@ -1,58 +0,0 @@
# Imports
from flask import Flask, request, render_template, send_from_directory, abort
from flask_session import Session
from dotenv import load_dotenv
from os import getenv as env, listdir
import logging, importlib
# Load env
load_dotenv()
# Create console log handler
console_log = logging.StreamHandler()
console_log.setFormatter(logging.Formatter("\033[1;32m%(asctime)s\033[0m - \033[1;34m%(levelname)s\033[0m - \033[1;31m%(name)s\033[0m - %(message)s"))
console_log.setLevel(logging.INFO)
# Create file log handler
file_log = logging.FileHandler(env('LOG_FILE', default='app.log'), mode=env('LOG_MODE', default='a'))
file_log.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s"))
file_log.setLevel(logging.DEBUG)
# Add handlers to the logger
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(console_log)
log.addHandler(file_log)
log.info("Logging initialized")
# Create flask app
app = Flask(
__name__,
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
static_folder=env('STATIC_FOLDER', default='../static')
)
# Configure sessions
app.config["SESSION_PERMANENT"] = True
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Load routes
routes_dir = env('ROUTES_DIR', default='src/routes')
for filename in listdir(routes_dir):
if not filename.endswith('.py') and filename.startswith('__'):
continue
module_name = f"{routes_dir.replace('/', '.')}.{filename[:-3]}"
try:
module = importlib.import_module(module_name)
if hasattr(module, 'bp'):
app.register_blueprint(module.bp)
log.info(f"Registered blueprint: {module_name}")
except Exception as e:
log.error(f"Failed to register blueprint {module_name}: {e}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

View File

Before

Width:  |  Height:  |  Size: 743 KiB

After

Width:  |  Height:  |  Size: 743 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 240 KiB

View File

@@ -1,6 +1,8 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /404.html Disallow: /404
Disallow: /500
Disallow: /400
Sitemap: https://alfieking.dev/sitemap.xml Sitemap: https://alfieking.dev/sitemap.xml

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://alfieking.dev/</loc>
</url>
<url>
<loc>https://alfieking.dev/toaster</loc>
</url>
<url>
<loc>https://alfieking.dev/events</loc>
</url>
<url>
<loc>https://alfieking.dev/events/paws-n-pistons</loc>
</url>
<url>
<loc>https://alfieking.dev/events/crittersmk</loc>
</url>
</urlset>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -1,155 +0,0 @@
#snakeContainer {
padding: 15px;
}
canvas#snakeCanvas {
box-sizing: border-box;
border: 2px solid var(--secondary-background-color);
border-radius: 10px;
width: 100%;
}
form {
display: flex;
flex-direction: column;
width: min-content;
gap: 1rem;
padding: 15px;
}
form input[type="text"] {
padding: 10px;
box-sizing: border-box;
border: 1px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
}
form button[type="submit"] {
padding: 10px;
box-sizing: border-box;
border: 1px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-weight: bold;
cursor: pointer;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
}
.min-width {
width: min-content;
}
.max-width {
width: 100%;
}
#snakeLeaderboardSection {
display: flex;
flex-direction: column;
align-items: center;
padding: 0;
max-height: 272px ;
}
#snakeLeaderboard {
display: flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
width: 100%;
overflow-y: scroll;
}
#snakeLeaderboard li {
padding: 5px 20px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-size: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
#snakeLeaderboard li:nth-child(even) {
background-color: var(--secondary-background-color);
}
dialog {
width: 90%;
max-width: 500px;
padding: 20px;
background-color: var(--background-color);
color: var(--text-color);
border: 2px solid var(--secondary-background-color);
border-radius: 8px;
}
dialog button {
padding: 10px;
width: 100%;
box-sizing: border-box;
border: 2px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-weight: bold;
cursor: pointer;
margin-top: 10px;
}
dialog h2 {
margin: 0;
font-size: 1.5rem;
}
dialog p {
margin: 0;
font-size: 1rem;
}
@media screen and (max-width: 850px) {
.flex-row {
flex-direction: column;
}
form {
align-items: center;
width: 100%;
}
.min-width {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
}
@media screen and (max-width: 650px) {
.mobileOnly {
display: flex;
}
.pcOnly {
display: none;
}
}
@media screen and (min-width: 651px) {
.mobileOnly {
display: none !important;
}
.pcOnly {
display: flex;
}
}

View File

@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wdth,wght@125,700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Fredoka:wdth,wght@125,700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@font-face { @font-face {
font-family:"Irken"; font-family:"Irken";
src:url("/static/content/Irken-Like-AllCaps.woff") format("woff"); src:url("/static/content/fonts/Irken-Like-AllCaps.woff") format("woff");
font-weight:normal; font-weight:normal;
font-style:normal; font-style:normal;
} }
@@ -227,7 +227,7 @@ main section a {
right: 0; right: 0;
bottom: 0; bottom: 0;
opacity: 0.1; opacity: 0.1;
background-image: url('/static/content/background.png'); background-image: url('/static/content/general_images/background.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 50% 0; background-position: 50% 0;
background-size: cover; background-size: cover;
@@ -304,6 +304,10 @@ main section a {
box-sizing: border-box; box-sizing: border-box;
} }
a {
text-decoration: none;
}
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
body { body {
background-color: var(--background-color); background-color: var(--background-color);

View File

@@ -1,21 +0,0 @@
cap-widget {
--cap-background: var(--secondary-background-color-but-slightly-transparent);
--cap-border-color: var(--secondary-background-color);
--cap-border-radius: 14px;
--cap-widget-height: 30px;
--cap-widget-width: 230px;
--cap-widget-padding: 14px;
--cap-gap: 15px;
--cap-color: var(--text-color);
--cap-checkbox-size: 25px;
--cap-checkbox-border: 1px solid var(--secondary-background-color);
--cap-checkbox-border-radius: 6px;
--cap-checkbox-background: none;
--cap-checkbox-margin: 2px;
--cap-font: "Space Mono", "serif";
--cap-spinner-color: var(--primary-color);
--cap-spinner-background-color: var(--secondary-background-color-but-slightly-transparent);
--cap-spinner-thickness: 5px;
--cap-credits-font-size: 12px;
--cap-opacity-hover: 0.8;
}

13
static/css/directory.css Normal file
View File

@@ -0,0 +1,13 @@
section#directory ul {
list-style: none;
padding: 0;
margin: 0;
}
section#directory li {
margin: 0.5rem 0;
}
section#directory a:hover {
scale: 1.05;
}

17
static/css/gallery.css Normal file
View File

@@ -0,0 +1,17 @@
.gallery {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
}
.gallery img {
max-width: 100%;
height: auto;
border-radius: 10px;
}
.gallery-date {
margin: 1rem 0 .25rem 0;
font-size: 2rem;
}

View File

@@ -4,6 +4,7 @@ ul#toaster-specs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
min-width: 400px;
} }
ul#toaster-specs li { ul#toaster-specs li {
@@ -33,6 +34,35 @@ ul#toaster-specs li {
height: 100%; height: 100%;
} }
.fur-meet-gallery-small {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.fur-meet-gallery-small img {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 10px;
border: 2px solid var(--secondary-background-color);
}
.fur-meet-gallery-small img:hover {
transform: scale(1.05);
transition: transform 0.2s ease-in-out;
cursor: pointer;
}
#fur-meets {
list-style: none;
padding: 0;
margin: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
@media screen and (max-width: 740px) { @media screen and (max-width: 740px) {
.flex-row { .flex-row {
flex-direction: column; flex-direction: column;
@@ -46,3 +76,9 @@ ul#toaster-specs li {
width: 100%; width: 100%;
} }
} }
@media screen and (max-width: 690px) {
.flex-col {
flex-direction: column;
}
}

View File

@@ -118,7 +118,7 @@ if (document.getElementById('spotify')) {
// load buttons // load buttons
function loadButtons() { function loadButtons() {
fetch('/static/content/buttons.txt').then(response => { fetch('/static/content/other/buttons.txt').then(response => {
return response.text(); return response.text();
}).then(data => { }).then(data => {
container = document.getElementById('button-collection'); container = document.getElementById('button-collection');

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.example.com/index.html</loc>
</url>
</urlset>

View File

@@ -4,18 +4,18 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Alfie's basement{% endblock %}</title> <title>{% block title %}Alfie's basement{% endblock %}</title>
<link rel="icon" href="/static/content/icon.webp"> <link rel="icon" href="/static/content/general_images/icon.webp">
<link rel="stylesheet" href="/static/css/base.css"> <link rel="stylesheet" href="/static/css/base.css">
<meta name="description" content="{% block description %}server backend survivor{% endblock %}"> <meta name="description" content="{% block description %}server backend survivor{% endblock %}">
<meta name="keywords" content="Alfie King, Alfie, King, Alfieking, Alfieking.dev, dev, server"> <meta name="keywords" content="{% block keywords %}Alfie King, Alfie, King, Alfieking, Alfieking.dev, dev, server, developer, backend, selfhost, homelab{% endblock %}">
<meta name="author" content="Alfie King"> <meta name="author" content="Alfie King">
<meta name="robots" content="all"> <meta name="robots" content="all">
<meta name="theme-color" content="#63de90" data-react-helmet="true"> <meta name="theme-color" content="#63de90" data-react-helmet="true">
<meta property="og:site_name" content="Alfieking.dev"> <meta property="og:site_name" content="Alfieking.dev">
<meta property="og:url" content="https://alfieking.dev"> <meta property="og:url" content="https://alfieking.dev/">
<meta property="og:title" content="{{ self.title() }}"> <meta property="og:title" content="{% block og-title %}Home - Alfie's basement{% endblock %}">
<meta property="og:description" content="{{ self.description() }}"> <meta property="og:description" content="{% block og-description %}server backend survivor{% endblock %}">
<meta property="og:image" content="/static/content/icon.webp"> <meta property="og:image" content="{% block og_image %}/static/content/general_images/icon.webp{% endblock %}">
{% block head %} {% block head %}
{% endblock %} {% endblock %}
</head> </head>
@@ -28,6 +28,7 @@
<ul> <ul>
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="/toaster">Toaster</a></li> <li><a href="/toaster">Toaster</a></li>
<li><a href="/events">Events</a></li>
<li><a href="https://git.alfieking.dev/acetheking987">Gitea</a></li> <li><a href="https://git.alfieking.dev/acetheking987">Gitea</a></li>
<li><a href="https://www.last.fm/user/acetheking987">LastFm</a></li> <li><a href="https://www.last.fm/user/acetheking987">LastFm</a></li>
<li><a href="https://prismic.alfieking.dev">Prismic</a></li> <li><a href="https://prismic.alfieking.dev">Prismic</a></li>
@@ -36,7 +37,6 @@
<li><a href="https://www.youtube.com/@acetheking987">YouTube</a></li> <li><a href="https://www.youtube.com/@acetheking987">YouTube</a></li>
<li><a href="https://acetheking987.tumblr.com/">Tumblr</a></li> <li><a href="https://acetheking987.tumblr.com/">Tumblr</a></li>
<li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li> <li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li>
<li><a href="/404">404 >:3</a></li>
</ul> </ul>
</section> </section>
</nav> </nav>
@@ -62,12 +62,12 @@
<section> <section>
<pre class="vsmoltext"> |\ _,,,---,,_<br>ZZZzz /,`.-'`' -. ;-;;,_<br> |,4- ) )-,_. ,\ ( `'-'<br> '---''(_/--' `-'\_)</pre> <pre class="vsmoltext"> |\ _,,,---,,_<br>ZZZzz /,`.-'`' -. ;-;;,_<br> |,4- ) )-,_. ,\ ( `'-'<br> '---''(_/--' `-'\_)</pre>
</section> </section>
<img src="/static/content/haj.gif" alt="haj" class="haj"> <img src="/static/content/general_images/haj.gif" alt="haj" class="haj">
</div> </div>
<main id="main"> <main id="main">
<header id="home"> <header id="home">
<div class="row"> <div class="row">
<img src="/static/content/icon.webp"> <img src="/static/content/general_images/icon.webp">
<div> <div>
<h1>Alfie King</h1> <h1>Alfie King</h1>
<h2 id="typing">server backend survivor</h2> <h2 id="typing">server backend survivor</h2>

View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
{% block description %}server backend survivor{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/directory.css">
{% endblock %}
{% block scripts %}
{% endblock %}
{% block content %}
<section id="directory">
<h1>/{{ directory }}</h1>
<ul>
{% for page in pages %}
<li><a href="/{{ directory }}{{ page.split('.')[0] }}">{{ page.split('.')[0] }}</a></li>
{% endfor %}
</ul>
</section>
{% endblock %}

View File

@@ -3,80 +3,18 @@
{% block title %}404 - Not Found{% endblock %} {% block title %}404 - Not Found{% endblock %}
{% block description %}The page you are looking for does not exist.{% endblock %} {% block description %}The page you are looking for does not exist.{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/404.css">
<link rel="stylesheet" href="/static/css/cap.css">
{% endblock %}
{% block content %} {% block content %}
<section> <section>
<h1>404</h1> <h1>404</h1>
<p> <p>
It seems like the thing you are looking for is not here :[ Hey so you know that thing you were looking for? Yeah, it doesn't exist. :P
<br><br> <br><br>
<span class="pcOnly"> So why not try going back to the <a href="/">homepage</a>?
while you're here, why not play some snake?
</span>
<span class="mobileOnly">
You can't play snake on mobile, sorry :(
</span>
</p> </p>
<div class="pcOnly" id="snakeContainer">
<canvas id="snakeCanvas"></canvas>
</div>
</section> </section>
<section class="pcOnly flex-row"> <section>
<section class="min-width"> <h2>The actual error for the 2 ppl who care</h2>
<h2>Submit score</h2> <p>
<form action="/snake/submit" method="POST" id="snakeForm"> 404: {{ requested_url }} not found :3
<input type="text" id="name" name="name" placeholder="Your name" required> </p>
<cap-widget id="captcha" data-cap-api-endpoint="https://cap.alfieking.dev/57d36430b9cb/api/"></cap-widget>
<input type="hidden" id="score" name="score" value="0">
<input type="hidden" id="game_token" name="game_token" value="{{ token}}">
<button type="submit" id="submit">Submit</button>
</form>
</section>
<section class="max-width" id="snakeLeaderboardSection">
<h2>Leaderboard</h2>
<ul id="snakeLeaderboard">
{% for score in scores %}
<li>
<span>{{ score.position }}</span>
<span>{{ score.name }}</span>
<span>{{ score.score }}</span>
</li>
{% endfor %}
</ul>
</section>
</section> </section>
<section class="max-width mobileOnly" id="snakeLeaderboardSection">
<h2>Leaderboard</h2>
<ul id="snakeLeaderboard">
{% for score in scores %}
<li>
<span>{{ score.position }}</span>
<span>{{ score.name }}</span>
<span>{{ score.score }}</span>
</li>
{% endfor %}
</ul>
</section>
{% if error %}
<dialog id="errorDialog">
<h2>Error</h2>
<p>{{ error }}</p>
<button onclick="errorDialog.close()">Close</button>
</dialog>
<script>
const errorDialog = document.getElementById('errorDialog');
if (errorDialog) {
errorDialog.showModal();
}
</script>
{% endif %}
{% endblock %}
{% block scripts %}
<script src="/static/js/snake.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
{% endblock %}

View File

@@ -2,12 +2,15 @@
{% block title %}Home - Alfie's basement{% endblock %} {% block title %}Home - Alfie's basement{% endblock %}
{% block description %}server backend survivor{% endblock %} {% block description %}server backend survivor{% endblock %}
{% block og-title %}Home - Alfie's basement{% endblock %}
{% block og-description %}server backend survivor{% endblock %}
{% block head %} {% block head %}
<link rel="stylesheet" href="/static/css/index.css"> <link rel="stylesheet" href="/static/css/index.css">
{% endblock %} {% endblock %}
{%block content %} {% block content %}
<section> <section>
<h1>A lil bit abt me</h1> <h1>A lil bit abt me</h1>
<p> <p>
@@ -104,6 +107,16 @@
<h1>Some News</h1> <h1>Some News</h1>
<h6>(dont expect this to be updated often tho :P)</h6> <h6>(dont expect this to be updated often tho :P)</h6>
<ul> <ul>
<li>
<h2>28-06-2025</h2>
<p>
I have updated the site a bit, I have added a few more featues, but the main update is that the site is now using flask as a backend.
I didn't want to use a framework at first, mainly because I like the simplicity of a static site, but it allows me to use templatiing and makes
adding new features easier and more organized. The site is also more interacive now, with a few secrets on some of the pages. I still plan on adding
more secrets and features. I also plan on adding a blog section, that I will move this to, so that I can give updates on the site and other things
that I find interesting.
</p>
</li>
<li> <li>
<h2>18-06-2025</h2> <h2>18-06-2025</h2>
<p> <p>

View File

@@ -0,0 +1,45 @@
{% extends "bases/base.html" %}
{% block title %}Critters MK - Alfie's basement{% endblock %}
{% block description %}furry corner{% endblock %}
{% block og_image %}/static/content/Toaster_v1.0_Dark.png{% endblock %}
{% block keywords %}
Alfie King, Alfie, King, Alfieking, Alfieking.dev, dev, server, developer, backend, selfhost, homelab, furry, protogen, toaster,
fursona, fur, furmeet, fursuit, persona, character, protogen fursona, protogen character, protogen fursona design,
protogen character design, critters mk, critters cmk, paws n pistons, paws'n'pistons, paws n pistons furry meet, paws'n'pistons furry meet,
protogen v1.0, toaster v1.0
{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/gallery.css">
{% endblock %}
{% block content %}
<section>
<h1>Critters MK</h1>
<p>
Critters Mk is a fur meet based in Milton Keynes, UK. They hold a meet once a month on saturdays around the area, usually at local parks. They are a
therian friendly group, and there are a variety of fursuiters and non-fursuiters that attend. The group is very welcoming to newcomers, and the meets
are a great way to meet new people in the furry community. There is also atleast one fursuit maker in the group to my knowledge, so if you are looking
for a fursuit, you may be able to find one there. The group is also very active on Telegram, and they have a Twitter page where they post updates about
the meets.
<br><br>
I have attended a few of the meets, and I enjoyed talking to the people there. I plan on attending every meet I can in the future, as well as any other local events
that I can find. If you are in the area, I highly recommend checking them out. You can find more information about the group on their
<a href="https://x.com/cmkfurmeet" target="_blank">Twitter page</a> (I don't know if I can link their Telegram group :<)
</p>
</section>
<section>
<h1>Photos :3</h1>
<p>
Here are some photos from the meets I have attended. I will add more as I attend more meets.
</p>
<h2 class="gallery-date">26th July 2025</h2>
<div class="gallery">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_152110445.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155134418.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155226274.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,64 @@
{% extends "bases/base.html" %}
{% block title %}Paws'N'Pistons - Alfie's basement{% endblock %}
{% block description %}furry corner{% endblock %}
{% block og_image %}/static/content/Toaster_v1.0_Dark.png{% endblock %}
{% block keywords %}
Alfie King, Alfie, King, Alfieking, Alfieking.dev, dev, server, developer, backend, selfhost, homelab, furry, protogen, toaster,
fursona, fur, furmeet, fursuit, persona, character, protogen fursona, protogen character, protogen fursona design,
protogen character design, critters mk, critters cmk, paws n pistons, paws'n'pistons, paws n pistons furry meet, paws'n'pistons furry meet,
protogen v1.0, toaster v1.0
{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/gallery.css">
<style>
#woooooo {
max-width: 50%;
height: auto;
border-radius: 10px;
margin: 10px;
}
@media (max-width: 600px) {
#woooooo {
max-width: 100%;
}
}
</style>
{% endblock %}
{% block content %}
<section>
<h1>Paws'N'Pistons</h1>
<p>
Paws'N'Pistons is a furry car meet that takes place across the UK. They are a very welcoming group that is open to all furries, regardless
of whether you have a car or not. Their meets last all day and involve multiple hours of driving across the country to various locations. They
also have a <a href="https://pawsnpistons.com" target="_blank">shop</a> where you can buy car stickers and other merch. They also hand out free
stickers at their meets. Their main social media is their <a href="https://www.instagram.com/paws_n_pistons/" target="_blank">Instagram</a>, where
they post photos from their meets and updates about upcoming events.
<br><br>
I've only attended one meet on the 3rd of August 2025, and it was a great experience. The people were very chill and I enjoyed driving around with them.
ALSO, one of them offered to let me try their fursuit!!! Im now going to speedrun going broke trying to get a fursuit of my own cus of this :3 (I was
already planning on getting one, but this just made me want one more).
<br>
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_200906329.jpg" alt="me in a fursuit" id="woooooo">
<br>
The fursuit belongs to <a href="https://www.tiktok.com/@trickythefox" target="_blank">Tricky the Fox</a>, they are a very chill person and I had a great time talking to them ^w^.
<h1>Photos :3</h1>
<p>
Here are some photos from the meets I have attended. I will add more as I attend more meets.
</p>
<h2 class="gallery-date">3rd Aug 2025</h2>
<div class="gallery">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141943558.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150138054.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150249916.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_183614897.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_140629639.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141242090.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_182023562.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_184321576.jpg" alt="Paws'N'Pistons">
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,107 @@
{% extends "bases/base.html" %}
{% block title %}Toaster - Alfie's basement{% endblock %}
{% block description %}furry corner{% endblock %}
{% block og_image %}/static/content/Toaster_v1.0_Dark.png{% endblock %}
{% block keywords %}
Alfie King, Alfie, King, Alfieking, Alfieking.dev, dev, server, developer, backend, selfhost, homelab, furry, protogen, toaster,
fursona, fur, furmeet, fursuit, persona, character, protogen fursona, protogen character, protogen fursona design,
protogen character design, critters mk, critters cmk, paws n pistons, paws'n'pistons, paws n pistons furry meet, paws'n'pistons furry meet,
protogen v1.0, toaster v1.0
{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/toaster.css">
{% endblock %}
{% block content %}
<section>
<h1>Toaster</h1>
<p>
heya, you may have guessed by now that I am a furry. Specifically my fursona is a protogen called Toaster. I didn't actually choose the name,
I just couldn't think of a name and a few people just started calling me Toaster because I am a protogen.
<br><br>
Originally (before I was a furry), I had a unnamed charcater that was just saved as "lil_guy.png" in my files. He is the tv head character
that I the main "mascot" of my website, I drew him a while ago when I was planning to make a functional tv head.
<br><br>
Once I eventually got out of the furry closet, I was trying to think of a species to choose and I thought that protogens are a mix of having a
screen for a face and being fluffy, so I thought it would be a good fit. I still want to keep the tv head character in some places since I rly like him,
however I plan on using Toaster more. So he may become the main mascot of my website "soon ish".
</p>
</section>
<div class="flex-row">
<section>
<h1>Specs</h1>
<h6>(Additional specs coming "soon ish", Very subject to change :P)</h6>
<ul id="toaster-specs">
<li><b>Species:</b><span>Protogen</span></li>
<li><b>Version:</b><span>v1.0</span></li>
<li><b>Height:</b><span>1.73m</span></li>
<li><b>Weight:</b><span>Mild Chonk</span></li>
<li><b>Base Color:</b><span class="color" style="background-color: #0e0c11">#0e0c11</span></li>
<li><b>Accent Color:</b><span class="color" style="background-color: #a685c6">#a685c6</span></li>
<li><b>Operating System:</b><span>Proot OS&trade;</span></li>
<li><b>Processor:</b><span>Fried Potato</span></li>
<li><b>RAM:</b><span>Not Enough</span></li>
<li><b>Storage:</b><span>1.44MB Floppy</span></li>
<li><b>Ports:</b><span>USB-C</span></li> <!-- You know exactly where this is dont you :3 (note: this is a joke (probably), there are ports are behind the round screens tho)-->
<li><b>Accessories:</b><span>"Neck Armor"</span></li>
<li><b>Bugs:</b><span>Anxiety<sup>2</sup></span></li>
<li><b>Gender:</b><span>Male</span></li>
</ul>
</section>
<div class="flex-col">
<img src="/static/content/toaster/Toaster_v1.0_Dark.png" alt="toaster" id="toaster-img">
<section class="fill-height">
<p>
NEW AND IMPROVED! Toaster v1.0 is here!
<br><br>
Toaster v1.0 is the first version of Toaster that I have drawn that I am actually happy with.
Im still working on the design, so it may change in the future, but I think I like this enough to keep it for now.
</p>
</section>
</div>
</div>
<section>
<h1>Plans</h1>
<p>
I plan on drawing Toaster "properly" soon, im quite happy with the current design, but I want to add more detail and personality to him.
I also want to make a proper ref sheet for him, so it looks like I have a decent idea of what im doing, cus im kinda winging it right now.
<br><br>
I also wanna try make a fursuit head of Toaster, but I am not sure how well that will go. I can handle the electronics and I know a few people
with 3d printers, so I can get the base printed. However I have never made a fursuit before, so idk how well it will go, expecially with the fur.
I have no clue how to make the fur look good, so I may just end up getting someone else to help me with that. Budget is also a concern, cus im
clinically broke. So rn im working of whatever I can buy from shady chinese websites for electronics and whatever I can find in my local area for the fur.
<br><br>
If I end up making a fursuit, I will probably make a post about it on my site and maybe even make a video of it (but dont hold me to that).
</p>
</section>
<section>
<h1>Events</h1>
<p>
There are a few events that ive been to, however I plan on trying to go to more in the future.
<br>
</p>
<ul id="fur-meets">
<li>
<a href="/events/crittersmk"><b>Critters MK</b></a> - A furmeet in Milton Keynes.
<div class="fur-meet-gallery-small">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_152110445.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155134418.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155226274.jpg" alt="Critters MK">
<img src="/static/content/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
</div>
</li>
<li>
<a href="/events/paws-n-pistons"><b>Paws'N'Pistons</b></a> - A furry car meet around the UK.
<div class="fur-meet-gallery-small">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141943558.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150138054.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150249916.jpg" alt="Paws'N'Pistons">
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_183614897.jpg" alt="Paws'N'Pistons">
</div>
</li>
<p>Click on the links to view more photos from each event :3</p>
</ul>
</section>
{% endblock %}

View File

@@ -1,63 +0,0 @@
{% extends "bases/base.html" %}
{% block title %}Toaster - Alfie's basement{% endblock %}
{% block description %}furry corner{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/toaster.css">
{% endblock %}
{% block content %}
<section>
<h1>Toaster</h1>
<p>
heya, you may have guessed by now that I am a furry. Specifically my fursona is a protogen called Toaster. I didn't actually choose the name,
I just couldn't think of a name and a few people just started calling me Toaster because I am a protogen.
<br><br>
Originally (before I was a furry), I had a unnamed charcater that was just saved as "lil_guy.png" in my files. He is the tv head character
that I the main "mascot" of my website, I drew him a while ago when I was planning to make a functional tv head.
<br><br>
Once I eventually got out of the furry closet, I was trying to think of a species to choose and I thought that protogens are a mix of having a
screen for a face and being fluffy, so I thought it would be a good fit. I still want to keep the tv head character in some places since I rly like him,
however I plan on hopefully drawing Toaster properly soon. Once i do, I may make him the main mascot of my website.
</p>
</section>
<div class="flex-row">
<section>
<h1>Specs</h1>
<h6>(Very subject to change, Im still trying to figure out what I want :P)</h6>
<ul id="toaster-specs">
<li><b>Species:</b><span>Protogen</span></li>
<li><b>Base Color:</b><span class="color" style="background-color: #000">#000000</span></li>
<li><b>Accent Color:</b><span class="color" style="background-color: #8136cd">#8136cd</span></li>
<li><b>Alt Accent Color:</b><span class="color" style="background-color: #1d147c">#1d147c</span></li>
<li><b>Operating System:</b><span>Proot OS&trade;</span></li>
<li><b>Processor:</b><span>Fried Potato</span></li>
<li><b>RAM:</b><span>Not Enough</span></li>
<li><b>Storage:</b><span>1.44MB Floppy</span></li>
</ul>
</section>
<div class="flex-col">
<img src="/static/content/toaster.png" alt="toaster" id="toaster-img">
<section class="fill-height">
<p>
A old image of Toaster, I will draw a new one soon. (Hopefully)
</p>
</section>
</div>
</div>
<section>
<h1>Plans</h1>
<p>
I plan on drawing Toaster properly soon, I have a few ideas for his design but I am not sure what I want to do yet.
I also want to make a proper ref sheet for him, so it looks like I have a decent idea of what im doing, cus im kinda winging it right now.
<br><br>
I also wanna try make a fursuit head of Toaster, but I am not sure how well that will go. I can handle the electronics and I know a few people
with 3d printers, so I can get the base printed. However I have never made a fursuit before, so idk how well it will go, expecially with the fur.
I have no clue how to make the fur look good, so I may just end up getting someone else to help me with that. Budget is also a concern, cus im
clinically broke, rn so Im working of whatever I can buy from shady chinese websites for electronics and whatever I can find in my local area for the fur.
<br><br>
If I end up making a fursuit, I will probably make a post about it on my site and maybe even make a video of it (but dont hold me to that).
</p>
</section>
{% endblock %}

1
thirdparty/inja vendored Submodule

Submodule thirdparty/inja added at 593ff96024

1
thirdparty/nlohmann vendored Submodule

Submodule thirdparty/nlohmann added at 000db7a6a2