Compare commits
	
		
			17 Commits
		
	
	
		
			da447939bb
			...
			c++-crow-b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6360b41e9a | |||
| 217adf91e9 | |||
| a7404fed3d | |||
| bfb1b8a21e | |||
| ef459d728a | |||
| 5690bcadf9 | |||
| 8b5b80f7c5 | |||
| 66806ad922 | |||
| b29a61a44b | |||
| a0562330a3 | |||
| df59a2c097 | |||
| 8209f52fa7 | |||
| b029eba456 | |||
| 1b2425a493 | |||
| e66f7e0588 | |||
| d48dd04af9 | |||
| f3d5cb9d53 | 
							
								
								
									
										33
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal 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
									
									
								
							
							
						
						@@ -1,5 +1,2 @@
 | 
			
		||||
.venv
 | 
			
		||||
.env
 | 
			
		||||
db.sqlite
 | 
			
		||||
flask_session
 | 
			
		||||
__pycache__
 | 
			
		||||
build/
 | 
			
		||||
.vscode/
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										25
									
								
								dockerfile
									
									
									
									
									
								
							
							
						
						@@ -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=main.py
 | 
			
		||||
 | 
			
		||||
# run the application
 | 
			
		||||
ENTRYPOINT [ "gunicorn",  "-b", ":5000", "--access-logfile", "-", "--error-logfile", "-", "src.main:app" ]
 | 
			
		||||
							
								
								
									
										15
									
								
								include/errors.hpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						@@ -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
									
								
							
							
						
						@@ -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)
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
python-dotenv
 | 
			
		||||
flask-session
 | 
			
		||||
requests
 | 
			
		||||
flask
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
import sqlite3
 | 
			
		||||
 | 
			
		||||
class Database:
 | 
			
		||||
    def __init__(self, db_name='db.sqlite'):
 | 
			
		||||
        self.connection = sqlite3.connect(db_name, check_same_thread=False)
 | 
			
		||||
        self.cursor = self.connection.cursor()
 | 
			
		||||
        self.create_snake_table()
 | 
			
		||||
 | 
			
		||||
    def create_snake_table(self):
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            CREATE TABLE IF NOT EXISTS snake (
 | 
			
		||||
                id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
                name TEXT NOT NULL,
 | 
			
		||||
                score INTEGER NOT NULL
 | 
			
		||||
            )
 | 
			
		||||
        ''')
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
 | 
			
		||||
    def insert_snake(self, name, score):
 | 
			
		||||
        old_score = self.get_snake_score(name)
 | 
			
		||||
        print(f"Old score for {name}: {old_score}")
 | 
			
		||||
        print(f"New score for {name}: {score}")
 | 
			
		||||
        if old_score is not None and score <= old_score:
 | 
			
		||||
            return
 | 
			
		||||
        
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            INSERT INTO snake (name, score)
 | 
			
		||||
            VALUES (?, ?)
 | 
			
		||||
        ''', (name, score))
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
 | 
			
		||||
    def get_snake_score(self, name):
 | 
			
		||||
        self.cursor.execute('SELECT score FROM snake WHERE name = ? ORDER BY score DESC LIMIT 1', (name,))
 | 
			
		||||
        result = self.cursor.fetchone()
 | 
			
		||||
        return result[0] if result else None
 | 
			
		||||
 | 
			
		||||
    def get_snake_scores(self):
 | 
			
		||||
        self.cursor.execute('SELECT * FROM snake ORDER BY score DESC')
 | 
			
		||||
        return self.cursor.fetchall()
 | 
			
		||||
    
 | 
			
		||||
    def close(self):
 | 
			
		||||
        self.connection.close()
 | 
			
		||||
							
								
								
									
										13
									
								
								src/errors.cpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						@@ -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();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								src/main.py
									
									
									
									
									
								
							
							
						
						@@ -1,117 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
import logging, requests
 | 
			
		||||
try:
 | 
			
		||||
    import src.database as database
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import database
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 | 
			
		||||
load_dotenv()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app = Flask(
 | 
			
		||||
    __name__,
 | 
			
		||||
    template_folder=env('TEMPLATE_FOLDER', default='../templates'),
 | 
			
		||||
    static_folder=env('STATIC_FOLDER', default='../static'),
 | 
			
		||||
    static_url_path=env('STATIC_URL_PATH', default='/static')
 | 
			
		||||
)
 | 
			
		||||
app.config["SESSION_PERMANENT"] = True
 | 
			
		||||
app.config["SESSION_TYPE"] = "filesystem"
 | 
			
		||||
Session(app)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
db = database.Database(db_name=env('DB_NAME', default='db.sqlite'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/')
 | 
			
		||||
def index():
 | 
			
		||||
    return render_template('index.html')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/robots.txt')
 | 
			
		||||
@app.route('/sitemap.xml')
 | 
			
		||||
@app.route('/favicon.ico')
 | 
			
		||||
def web_stuffs():
 | 
			
		||||
    return send_from_directory(
 | 
			
		||||
        app.static_folder,
 | 
			
		||||
        request.path[1:],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/<path:path>')
 | 
			
		||||
def catch_all(path):
 | 
			
		||||
    try:
 | 
			
		||||
        return render_template(path + '.html')
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        abort(404)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/404')
 | 
			
		||||
@app.errorhandler(404)
 | 
			
		||||
def not_found():
 | 
			
		||||
    unformatted_scores = db.get_snake_scores()
 | 
			
		||||
    scores = [{'position': i + 1, 'name': score[1], 'score': score[2]} for i, score in enumerate(unformatted_scores)]
 | 
			
		||||
    return render_template('404.html', scores=scores)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/404/submit', methods=['POST'])
 | 
			
		||||
def snake_submit():
 | 
			
		||||
    unformatted_scores = db.get_snake_scores()
 | 
			
		||||
    scores = [{'position': i + 1, 'name': score[1], 'score': score[2]} for i, score in enumerate(unformatted_scores)]
 | 
			
		||||
 | 
			
		||||
    data = request.form
 | 
			
		||||
    username = data.get('username', '').strip()
 | 
			
		||||
    score = data.get('snake-score', '').strip()
 | 
			
		||||
    token = data.get('cap-token', '').strip()
 | 
			
		||||
 | 
			
		||||
    if not username or not score or not token:
 | 
			
		||||
        logging.error("Missing required fields: username=%s, score=%s, token=%s", username, score, token)
 | 
			
		||||
        return render_template('404.html', scores=scores, error='Missing required fields'), 400
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        score = int(score)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        logging.error("Invalid score value: %s", score)
 | 
			
		||||
        return render_template('404.html', scores=scores, error='Invalid score value'), 400
 | 
			
		||||
    
 | 
			
		||||
    if score <= 0 or score > 10000 or len(username) < 3 or len(username) > 15:
 | 
			
		||||
        logging.error("Invalid score or username length: score=%s, username=%s", score, username)
 | 
			
		||||
        return render_template('404.html', scores=scores, error='Invalid score or username length'), 400
 | 
			
		||||
 | 
			
		||||
    cap_response = requests.post(
 | 
			
		||||
        env('CAP_VERIFY_URL', default='https://<instance_url>/<key_id>/siteverify'),
 | 
			
		||||
        json={
 | 
			
		||||
            'secret': env('CAP_SECRET', default=''),
 | 
			
		||||
            'response': token,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if cap_response.status_code != 200 or not cap_response.json().get('success', "false") != "true":
 | 
			
		||||
        logging.error("Captcha verification failed: %s", cap_response.json())
 | 
			
		||||
        return render_template('404.html', scores=scores, error='Captcha verification failed'), 400
 | 
			
		||||
    
 | 
			
		||||
    db.insert_snake(name=username, score=int(score))
 | 
			
		||||
    logging.info("Snake submitted: name=%s, score=%d", username, score)
 | 
			
		||||
 | 
			
		||||
    unformatted_scores = db.get_snake_scores()
 | 
			
		||||
    scores = [{'position': i + 1, 'name': score[1], 'score': score[2]} for i, score in enumerate(unformatted_scores)]
 | 
			
		||||
    return render_template('404.html', scores=scores, success='Score submitted successfully!')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/500')
 | 
			
		||||
@app.errorhandler(500)
 | 
			
		||||
def internal_error(error="An internal server error occurred."):
 | 
			
		||||
    logging.error("Internal server error: %s", error)
 | 
			
		||||
    return render_template('500.html'), 500
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app.run(
 | 
			
		||||
        host=env('HOST', default='0.0.0.0'), 
 | 
			
		||||
        port=env('PORT', default=5000),
 | 
			
		||||
        debug=env('DEBUG', default="false").lower() == 'true'
 | 
			
		||||
    )
 | 
			
		||||
							
								
								
									
										76
									
								
								src/templating.cpp
									
									
									
									
									
										Normal 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{});
 | 
			
		||||
}
 | 
			
		||||
| 
		 After Width: | Height: | Size: 3.6 MiB  | 
| 
		 After Width: | Height: | Size: 4.6 MiB  | 
| 
		 After Width: | Height: | Size: 3.2 MiB  | 
| 
		 After Width: | Height: | Size: 2.0 MiB  | 
| 
		 After Width: | Height: | Size: 4.6 MiB  | 
| 
		 After Width: | Height: | Size: 3.3 MiB  | 
| 
		 After Width: | Height: | Size: 3.5 MiB  | 
| 
		 After Width: | Height: | Size: 2.7 MiB  | 
| 
		 After Width: | Height: | Size: 3.5 MiB  | 
| 
		 After Width: | Height: | Size: 3.3 MiB  | 
| 
		 After Width: | Height: | Size: 4.0 MiB  | 
| 
		 After Width: | Height: | Size: 4.5 MiB  | 
| 
		 After Width: | Height: | Size: 4.7 MiB  | 
| 
		 Before Width: | Height: | Size: 743 KiB After Width: | Height: | Size: 743 KiB  | 
| 
		 Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB  | 
| 
		 Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB  | 
| 
		 Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 240 KiB  | 
@@ -1,6 +1,8 @@
 | 
			
		||||
User-agent: *
 | 
			
		||||
Allow: /
 | 
			
		||||
Disallow: /404.html
 | 
			
		||||
Disallow: /404
 | 
			
		||||
Disallow: /500
 | 
			
		||||
Disallow: /400
 | 
			
		||||
 | 
			
		||||
Sitemap: https://alfieking.dev/sitemap.xml
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								static/content/other/sitemap.xml
									
									
									
									
									
										Normal 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>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 40 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/content/toaster/Toaster_v1.0_Dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.7 MiB  | 
							
								
								
									
										3
									
								
								static/css/400.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
section h2 {
 | 
			
		||||
    margin-top: 2rem !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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');
 | 
			
		||||
@font-face {
 | 
			
		||||
    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-style:normal;
 | 
			
		||||
}
 | 
			
		||||
@@ -227,7 +227,7 @@ main section a {
 | 
			
		||||
    right: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    opacity: 0.1;
 | 
			
		||||
    background-image: url('/static/content/background.png');
 | 
			
		||||
    background-image: url('/static/content/general_images/background.png');
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-position: 50% 0;
 | 
			
		||||
    background-size: cover;
 | 
			
		||||
@@ -304,6 +304,10 @@ main section a {
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 1000px) {
 | 
			
		||||
    body {
 | 
			
		||||
        background-color: var(--background-color);
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						@@ -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
									
								
							
							
						
						@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ ul#toaster-specs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 2px;
 | 
			
		||||
    min-width: 400px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul#toaster-specs li {
 | 
			
		||||
@@ -33,6 +34,35 @@ ul#toaster-specs li {
 | 
			
		||||
    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) {
 | 
			
		||||
    .flex-row {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
@@ -45,4 +75,10 @@ ul#toaster-specs li {
 | 
			
		||||
    #toaster-img {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 690px) {
 | 
			
		||||
    .flex-col {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -118,7 +118,7 @@ if (document.getElementById('spotify')) {
 | 
			
		||||
// load buttons
 | 
			
		||||
 | 
			
		||||
function loadButtons() {
 | 
			
		||||
    fetch('/static/content/buttons.txt').then(response => {
 | 
			
		||||
    fetch('/static/content/other/buttons.txt').then(response => {
 | 
			
		||||
        return response.text();
 | 
			
		||||
    }).then(data => {
 | 
			
		||||
        container = document.getElementById('button-collection');
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,7 @@ function gameLoop() {
 | 
			
		||||
        setTimeout(gameLoop, 100);
 | 
			
		||||
    } else {
 | 
			
		||||
        document.removeEventListener('keydown', changeDirection);
 | 
			
		||||
        document.getElementById('snake-score').value = score;
 | 
			
		||||
        document.getElementById('score').value = score;
 | 
			
		||||
        alert(`Game Over! Your score: ${score}`);
 | 
			
		||||
        menu();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -1,81 +0,0 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}404 - Not Found{% 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 %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h1>404</h1>
 | 
			
		||||
    <p>
 | 
			
		||||
        It seems like the thing you are looking for is not here :[
 | 
			
		||||
        <br><br>
 | 
			
		||||
        <span class="pcOnly">
 | 
			
		||||
        while you're here, why not play some snake?
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="mobileOnly">
 | 
			
		||||
        You can't play snake on mobile, sorry :(
 | 
			
		||||
        </span>
 | 
			
		||||
    </p>
 | 
			
		||||
    <div class="pcOnly" id="snakeContainer">
 | 
			
		||||
        <canvas id="snakeCanvas"></canvas>
 | 
			
		||||
    </div>
 | 
			
		||||
</section>
 | 
			
		||||
<section class="pcOnly flex-row">
 | 
			
		||||
    <section class="min-width">
 | 
			
		||||
        <h2>Submit score</h2>
 | 
			
		||||
        <form action="/404/submit" method="POST" id="snakeForm">
 | 
			
		||||
            <input type="text" id="username" name="username" placeholder="Your name" required>
 | 
			
		||||
            <cap-widget id="captcha" data-cap-api-endpoint="https://cap.alfieking.dev/57d36430b9cb/api/"></cap-widget>
 | 
			
		||||
            <input type="hidden" id="snake-score" name="snake-score" value="0">
 | 
			
		||||
            <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 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 %}
 | 
			
		||||
@@ -4,18 +4,18 @@
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <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">
 | 
			
		||||
    <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="robots" content="all">
 | 
			
		||||
    <meta name="theme-color" content="#63de90" data-react-helmet="true">
 | 
			
		||||
    <meta property="og:site_name" content="Alfieking.dev">
 | 
			
		||||
    <meta property="og:url" content="https://alfieking.dev">
 | 
			
		||||
    <meta property="og:title" content="{{ self.title() }}">
 | 
			
		||||
    <meta property="og:description" content="{{ self.description() }}">
 | 
			
		||||
    <meta property="og:image" content="/static/content/icon.webp">
 | 
			
		||||
    <meta property="og:url" content="https://alfieking.dev/">
 | 
			
		||||
    <meta property="og:title" content="{% block og-title %}Home - Alfie's basement{% endblock %}">
 | 
			
		||||
    <meta property="og:description" content="{% block og-description %}server backend survivor{% endblock %}">
 | 
			
		||||
    <meta property="og:image" content="{% block og_image %}/static/content/general_images/icon.webp{% endblock %}">
 | 
			
		||||
    {% block head %}
 | 
			
		||||
    {% endblock %}
 | 
			
		||||
</head>
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <li><a href="/">Home</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://www.last.fm/user/acetheking987">LastFm</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://acetheking987.tumblr.com/">Tumblr</a></li>
 | 
			
		||||
                    <li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li>
 | 
			
		||||
                    <li><a href="/404">404 >:3</a></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </section>
 | 
			
		||||
        </nav>
 | 
			
		||||
@@ -62,12 +62,12 @@
 | 
			
		||||
        <section>
 | 
			
		||||
            <pre class="vsmoltext">      |\      _,,,---,,_<br>ZZZzz /,`.-'`'    -.  ;-;;,_<br>     |,4-  ) )-,_. ,\ (  `'-'<br>    '---''(_/--'  `-'\_)</pre>
 | 
			
		||||
        </section>
 | 
			
		||||
        <img src="/static/content/haj.gif" alt="haj" class="haj">
 | 
			
		||||
        <img src="/static/content/general_images/haj.gif" alt="haj" class="haj">
 | 
			
		||||
    </div>
 | 
			
		||||
    <main id="main">
 | 
			
		||||
        <header id="home">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <img src="/static/content/icon.webp">
 | 
			
		||||
                <img src="/static/content/general_images/icon.webp">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <h1>Alfie King</h1>
 | 
			
		||||
                    <h2 id="typing">server backend survivor</h2>
 | 
			
		||||
							
								
								
									
										22
									
								
								templates/bases/directory.html
									
									
									
									
									
										Normal 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 %}
 | 
			
		||||
							
								
								
									
										29
									
								
								templates/errors/400.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,29 @@
 | 
			
		||||
{% extends "bases/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}400 - Internal Server Error{% endblock %}
 | 
			
		||||
{% block description %}Bad request. The server could not understand the request due to invalid syntax.{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="/static/css/400.css">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h1>400 - Bad Request</h1>
 | 
			
		||||
    <p>
 | 
			
		||||
        What did you do? The server could not understand the request due to invalid syntax. Please check your request and try again.
 | 
			
		||||
    </p>
 | 
			
		||||
    <h2>The fuckup in question</h2>
 | 
			
		||||
    <p>
 | 
			
		||||
        {% if error %}
 | 
			
		||||
            {{ error }}
 | 
			
		||||
        {% else %}
 | 
			
		||||
            No specific error message provided.
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </p>
 | 
			
		||||
    <h2>What to do now</h2>
 | 
			
		||||
    <p>
 | 
			
		||||
        idk :P
 | 
			
		||||
    </p>
 | 
			
		||||
</section>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										20
									
								
								templates/errors/404.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,20 @@
 | 
			
		||||
{% extends "bases/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}404 - Not Found{% endblock %}
 | 
			
		||||
{% block description %}The page you are looking for does not exist.{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h1>404</h1>
 | 
			
		||||
    <p>
 | 
			
		||||
        Hey so you know that thing you were looking for? Yeah, it doesn't exist. :P
 | 
			
		||||
        <br><br>
 | 
			
		||||
        So why not try going back to the <a href="/">homepage</a>?
 | 
			
		||||
    </p>
 | 
			
		||||
</section>
 | 
			
		||||
<section>
 | 
			
		||||
    <h2>The actual error for the 2 ppl who care</h2>
 | 
			
		||||
    <p>
 | 
			
		||||
        404: {{ requested_url }} not found :3
 | 
			
		||||
    </p>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% extends "bases/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}500 - Internal Server Error{% endblock %}
 | 
			
		||||
{% block description %}An unexpected error occurred on the server.{% endblock %}
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% extends "bases/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Home - Alfie's basement{% endblock %}
 | 
			
		||||
{% block description %}server backend survivor{% endblock %}
 | 
			
		||||
{% block og-title %}Home - Alfie's basement{% endblock %}
 | 
			
		||||
{% block og-description %}server backend survivor{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="/static/css/index.css">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{%block content %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h1>A lil bit abt me</h1>
 | 
			
		||||
    <p>
 | 
			
		||||
@@ -104,6 +107,16 @@
 | 
			
		||||
    <h1>Some News</h1>
 | 
			
		||||
    <h6>(dont expect this to be updated often tho :P)</h6>
 | 
			
		||||
    <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>
 | 
			
		||||
            <h2>18-06-2025</h2>
 | 
			
		||||
            <p>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								templates/pages/events/crittersmk.html
									
									
									
									
									
										Normal 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 %}
 | 
			
		||||
							
								
								
									
										64
									
								
								templates/pages/events/paws-n-pistons.html
									
									
									
									
									
										Normal 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 %}
 | 
			
		||||
							
								
								
									
										107
									
								
								templates/pages/toaster.html
									
									
									
									
									
										Normal 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™</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 %}
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
{% extends "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™</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 %}
 | 
			
		||||