3 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
95 changed files with 499 additions and 1038 deletions

View File

@@ -1,65 +1,33 @@
name: Deploy website
run-name: ${{ gitea.actor }} is deploying update
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:
deploy:
build-and-push-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
set -e
APP_DIR=/var/www/alfieking.dev
RELEASES_DIR=$APP_DIR/releases
TIMESTAMP=$(date +%Y%m%d%H%M%S)
NEW_RELEASE=$RELEASES_DIR/$TIMESTAMP
KEEP_RELEASES=5
echo "Creating release directory..."
mkdir -p $NEW_RELEASE
echo "Cloning public repository..."
git clone --depth 1 -b main \
https://git.alfieking.dev/acetheking987/alfieking.dev.git \
$NEW_RELEASE
echo "Installing dependencies..."
source $APP_DIR/venv/bin/activate
pip install -r $NEW_RELEASE/requirements.txt
echo "Switching symlink..."
ln -sfn $NEW_RELEASE $APP_DIR/current
echo "Changing ownership..."
chown -R www-data:www-data /var/www/alfieking.dev/releases/$TIMESTAMP
chown -R www-data:www-data /var/www/alfieking.dev/current
echo "Restarting Gunicorn (downtime incomming XD)..."
systemctl restart alfieking.dev.service
echo "Cleaning old releases..."
CURRENT_TARGET=$(readlink -f $APP_DIR/current)
cd $RELEASES_DIR
for dir in $(ls -dt */ | tail -n +$((KEEP_RELEASES+1))); do
FULL_PATH="$RELEASES_DIR/${dir%/}"
if [ "$FULL_PATH" != "$CURRENT_TARGET" ]; then
rm -rf "$FULL_PATH"
fi
done
echo "Deployment successful."
EOF
- 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

5
.gitignore vendored
View File

@@ -1,3 +1,2 @@
.env
__pycache__
.venv
build/
.vscode/

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

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,7 +0,0 @@
psycopg2-binary
python-dotenv
flask-session
requests
flask
markdown
gunicorn

View File

@@ -1,18 +0,0 @@
from os import getenv as env
import psycopg2, logging
log = logging.getLogger("blog")
log.info("connecting to database")
conn = psycopg2.connect(
host = env("PG_HOST"),
port = env("PG_PORT"),
dbname = env("PG_DBNAME"),
user= env("PG_USER"),
password = env("PG_PASSWORD")
)
cursor = conn.cursor()

View File

@@ -1,60 +0,0 @@
# Imports
from flask import Blueprint, render_template, abort, request
import os, markdown
try:
from src.name import get_name
except ImportError:
from name import get_name
# Create blueprint
bp = Blueprint('dynamic_routes', __name__)
template_folder = "templates"
# helper func
def get_path(file) -> str:
return os.path.join(template_folder, "pages", file)
# Get all files in folder
def ListFiles(path):
path = get_path(path)
files = []
for root, dirs, files_in_dir in os.walk(path):
for file in files_in_dir:
files.append(os.path.relpath(os.path.join(root, file), path))
for dir in dirs:
files.append(os.path.relpath(os.path.join(root, dir), path) + '/')
return files
# Catch-all route for generic pages
@bp.route('/<path:filename>')
def catch_all(filename):
if os.path.exists(get_path(filename)):
if os.path.isdir(get_path(filename)):
return render_template(
'bases/directory.html',
directory=filename + "/" if not filename.endswith('/') else filename,
pages=ListFiles(filename),
name=get_name(request)
)
return render_template(f'pages/{filename}', name=get_name(request))
elif os.path.exists(get_path(filename + '.html')):
return render_template(f'pages/{filename}.html', name=get_name(request))
elif os.path.exists(get_path(filename + '.md')):
output = markdown.markdown(open(get_path(filename + '.md'), "r").read())
return render_template(
f'bases/md.html',
title = filename.split("/")[-1],
markdown = output,
name=get_name(request)
)
else:
abort(404, f"'{filename}' not found")

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);
}
}

View File

@@ -1,44 +0,0 @@
from flask import Blueprint, render_template, request
from werkzeug.exceptions import HTTPException
try:
from src.name import get_name
except ImportError:
from name import get_name
bp = Blueprint("errors", __name__)
@bp.route('/500')
@bp.app_errorhandler(500)
def internal_server_error(error:HTTPException=None):
return render_template('errors/500.html', error=error, name=get_name(request)), 500
@bp.route('/404')
@bp.app_errorhandler(404)
def not_found(error:HTTPException=None):
return render_template('errors/404.html', error=error, name=get_name(request)), 404
@bp.route('/400')
@bp.app_errorhandler(400)
def bad_request(error:HTTPException=None):
return render_template('errors/400.html', error=error, name=get_name(request)), 400
@bp.route('/idk')
@bp.app_errorhandler(Exception)
def idk(error:HTTPException=None):
if not isinstance(error, HTTPException):
error = HTTPException("I honestly dont know")
error.code = 418
return render_template(
'errors/error.html',
code = error.code,
description = error.description,
err_name = error.name,
name=get_name(request)
), error.code

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,80 +0,0 @@
# IMPORTS
from flask import Flask, render_template, request
from os import getenv as env
import logging
try:
import src.dynamic_routes as dynamic_routes
import src.errors as errors
import src.pg_log as pg_log
from src.name import get_name
except ImportError:
import dynamic_routes, errors, pg_log
from name import get_name
from dotenv import load_dotenv
load_dotenv()
# LOGGING
pg_log_handler = pg_log.PgLog(
host = env("PG_HOST"),
port = env("PG_PORT"),
dbname = env("PG_DBNAME"),
user= env("PG_USER"),
password = env("PG_PASSWORD")
)
pg_log_handler.setLevel(logging.DEBUG)
stream_log_handler = logging.StreamHandler()
stream_log_handler.setFormatter(
logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
)
stream_log_handler.setLevel(logging.INFO)
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
log.addHandler(stream_log_handler)
log.addHandler(pg_log_handler)
werkzeug_logger = logging.getLogger("werkzeug")
werkzeug_logger.setLevel(logging.DEBUG)
werkzeug_logger.addHandler(pg_log_handler)
werkzeug_logger.addHandler(stream_log_handler)
log.info("Logging initialized.")
# CREATE FLASK APP
app = Flask(
__name__,
template_folder="../templates",
static_folder="../static"
)
log.info("Flask initialized.")
# BLUEPRINTS
app.register_blueprint(errors.bp, url_prefix="/errors")
app.register_blueprint(dynamic_routes.bp, url_prefix="/")
log.info("Blueprints registered.")
# ROUTES
@app.route("/")
def index():
return render_template("index.html", name=get_name(request))
@app.route("/toaster")
def toaster():
return render_template("toaster.html", name=get_name(request))
@app.route("/terminal")
def terminal():
return render_template("terminal.html", name=get_name(request))
# DEBUG (DONT RUN LIKE THIS IN PROD)
if __name__ == "__main__":
log.warning(f"RUNNING IN DEBUG MODE DO NOT USE FOR PRODUCTION!")
app.run(debug=True)

View File

@@ -1,5 +0,0 @@
def get_name(req):
if req.headers.get("Host") == "proot.uk":
return "Toaster"
else:
return "Alfie King"

View File

@@ -1,35 +0,0 @@
import psycopg2, logging
class PgLog(logging.StreamHandler):
def __init__(self, host, port, dbname, user, password):
super().__init__()
self.host = host
self.port = port
self.dbname = dbname
self.user = user
self.password = password
self.conn = psycopg2.connect(
database = dbname,
user = user,
password = password,
host = host,
port = port
)
self.cursor = self.conn.cursor()
def __del__(self):
self.cursor.close()
self.conn.close()
def emit(self, record):
self.cursor.execute(
f"INSERT INTO logs (level, message) VALUES (%s, %s)",
(
record.levelname,
record.getMessage(),
)
)
self.conn.commit()

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{});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -3,6 +3,7 @@ https://cyber.dabamos.de/88x31/anythingbut.gif
https://cyber.dabamos.de/88x31/bestdesktop.gif
https://kopawz.neocities.org/buttonhoard/buttonsfldr2/diagnosedwithGAY.gif
https://kopawz.neocities.org/indexgraphics/buttondecor/ilikecomputer.png
https://identity-crisis.carrd.co/assets/images/gallery04/ad4f8d52.jpg?v=4e55d939
https://anlucas.neocities.org/best_viewed_with_eyes.gif
https://anlucas.neocities.org/html_learn_it_today.gif
https://highway.eightyeightthirty.one/badge/5d58a8f32b007d4897db6f862a895a81674fb35f5cc3947fc66595817ca174db

View File

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

View File

@@ -6,4 +6,13 @@
<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: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 KiB

View File

@@ -5,24 +5,6 @@
font-weight:normal;
font-style:normal;
}
@font-face {
font-family:"Scratch";
src:url("/static/content/fonts/avali-scratch.otf.woff2") format("woff2");
font-weight:normal;
font-style:normal;
}
@font-face {
font-family:"Ultrafont";
src:url("/static/content/fonts/ultrakill-font.woff2") format("woff2");
font-weight:normal;
font-style:normal;
}
@font-face {
font-family:"Ultrafont2";
src:url("/static/content/fonts/ultrakill-font-2.woff2") format("woff2");
font-weight:normal;
font-style:normal;
}
:root {
--primary-color: #5cdd8b;
@@ -35,11 +17,6 @@
--font-family: "Space Mono", "serif";
--title-font: 'Roboto Mono', sans-serif;
--irken-font: 'Irken';
--scratch-font: 'Scratch';
--ultrafont-font: 'Ultrafont';
--smileos2-box: url(/static/content/smileos/SmileOS_2_Box.webp) 17 3 3 fill / 51px 9px 9px;
--smileos2-font: 'Ultrafont2';
--smileos2-emphasis: #FF4343;
}
body {
@@ -243,15 +220,6 @@ main section a {
text-decoration: none;
}
.smileos {
border-image: var(--smileos2-box);
padding: 54px 15px 12px;
image-rendering: pixelated;
font-size: 1.25rem;
font-family: var(--smileos2-font);
color: #fff;
}
#furry {
position: fixed;
left: 0;
@@ -276,14 +244,6 @@ main section a {
font-family: var(--irken-font);
}
.scratch {
font-family: var(--scratch-font);
}
.ultrafont {
font-family: var(--ultrafont-font);
}
#alt-nav {
display: none;
backdrop-filter: blur(2px) brightness(0.6);
@@ -348,41 +308,6 @@ a {
text-decoration: none;
}
code {
background-color: var(--secondary-background-color-but-slightly-transparent);
padding: 4px 6px;
border-radius: 4px;
font-size: 0.9rem;
color: var(--primary-color);
}
#toaster-wave {
position: absolute;
left: -145px;
top: 200px;
}
.webring {
text-align: center;
}
.webring a {
text-decoration: none;
color: var(--primary-color);
font-size: 1.2rem;
}
.webring a:hover {
font-weight: 900;
text-shadow: 0px 0px 10px var(--primary-color-but-slightly-transparent);
}
@media screen and (max-width: 1240px) {
#toaster-wave {
display: none;
}
}
@media screen and (max-width: 1000px) {
body {
background-color: var(--background-color);

View File

@@ -1,23 +1,17 @@
.gallery {
width: 100%;
}
.gallery .gallery-images {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
overflow: hidden;
}
.gallery .gallery-images img {
.gallery img {
max-width: 100%;
height: auto;
border-radius: 10px;
}
.gallery h2.gallery-date {
position: relative;
top: 0;
left: 0;
.gallery-date {
margin: 1rem 0 .25rem 0;
font-size: 2rem;
}

View File

@@ -1,131 +0,0 @@
@font-face {
font-family:"Ultrafont2";
src:url("/static/content/fonts/ultrakill-font-2.woff2") format("woff2");
font-weight:normal;
font-style:normal;
}
#terminal {
aspect-ratio: 4/3;
padding: 0;
font-family: "Ultrafont2";
font-size: 1.2rem;
color: #ffffff;
border: none;
}
.smileos-header {
height: 9%;
border-image: url(/static/content/smileos/SmileOS_2_Header.webp) 3 fill / 9px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0;
font-size: 1.4rem;
font-weight: 900;
padding-left: 7px;
padding-right: 7px;
image-rendering: pixelated;
}
.smileos-header img {
height: 100%;
object-fit: contain;
}
#terminal-container {
border-image: url(/static/content/smileos/SmileOS_2_Content.webp) 1 3 3 fill / 3px 9px 9px;
height: 100%;
display: grid;
grid-template-areas:
"logo window"
"buttons window";
grid-template-columns: 40% 1fr;
grid-template-rows: 25% 1fr;
padding: 15px;
box-sizing: border-box;
image-rendering: pixelated;
}
#smileos-logo {
width: 250px;
grid-area: logo;
margin: auto;
image-rendering: pixelated;
}
#terminal-window {
height: 100%;
grid-area: window;
}
#window-container {
border-image: url(/static/content/smileos/SmileOS_2_Content.webp) 1 3 3 fill / 3px 9px 9px;
height: 91%;
padding: 35px;
box-sizing: border-box;
image-rendering: pixelated;
}
#window-content {
border-image: url(/static/content/smileos/SmileOS_2_inset_panel.webp) 1 fill / 3px;
height: 100%;
box-sizing: border-box;
padding: 15px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
image-rendering: pixelated;
}
.red {
color: #ff4343;
}
#terminal-buttons {
grid-area: buttons;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.terminal-button {
border-image: url(/static/content/smileos/SmileOS_2_Button_transparent.png) 2 / 9px;
background: url(/static/content/smileos/SmileOS_2_Button_Background.png);
background-repeat: repeat-x;
background-size: 100% 100%;
border-radius: 15px;
height: 75px;
width: 270px;
image-rendering: pixelated;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-family: "Ultrafont2";
font-size: 1.2rem;
color: #ffffff;
}
.terminal-button:hover {
filter: contrast(110%);
}
.terminal-button:active {
filter: contrast(125%);
}
#smileos-window-content img {
width: 100%;
}
#smileos-restart-music {
color: #FF4343;
cursor: pointer;
}
#smileos-restart-music:hover {
font-weight: 700;
}

View File

@@ -81,8 +81,4 @@ ul#toaster-specs li {
.flex-col {
flex-direction: column;
}
}
#toaster-wave {
display: none;
}

View File

@@ -72,33 +72,25 @@ typing();
// HIDDEN STUFF (shh don't tell anyone >:3)
let last15Chars = "";
let last5Chars = "";
document.addEventListener('keydown', function(event) {
last15Chars += event.key;
if (last15Chars.includes("furry")) {
last5Chars += event.key;
if (last5Chars == "furry") {
console.log("owo, whats this?");
document.getElementById('furry').style.display = 'block';
last15Chars = "";
}
if (last15Chars.includes("irken")) {
if (last5Chars == "irken") {
console.log("doom doom doom!");
document.querySelector(":root").style.setProperty('--font-family', 'Irken');
document.querySelector(":root").style.setProperty('--title-font', '1.5em');
last15Chars = "";
}
if (last15Chars.includes("scratch")) {
console.log("space chicken");
document.querySelector(":root").style.setProperty('--font-family', 'Scratch');
document.querySelector(":root").style.setProperty('--title-font', '1em');
last15Chars = "";
}
while (last15Chars.length >= 15) {
last15Chars = last15Chars.slice(1);
while (last5Chars.length >= 5) {
last5Chars = last5Chars.slice(1);
}
});
// Spotify API (now lastfm)
// Spotify API
function getSpotify() {
fetch('https://api.alfieking.dev/spotify/nowplaying/xz02oolstlvwxqu1pfcua9exz').then(response => {
@@ -123,11 +115,10 @@ if (document.getElementById('spotify')) {
setInterval(getSpotify, 15000);
}
// load buttons
function loadButtons() {
fetch('/static/content/buttons/non_link_buttons.txt').then(response => {
fetch('/static/content/other/buttons.txt').then(response => {
return response.text();
}).then(data => {
container = document.getElementById('button-collection');

View File

@@ -1,76 +0,0 @@
// onionring.js is made up of four files - onionring-widget.js (this one!), onionring-index.js, onionring-variables.js and onionring.css
// it's licensed under the cooperative non-violent license (CNPL) v4+ (https://thufie.lain.haus/NPL.html)
// it was originally made by joey + mord of allium (蒜) house, last updated 2020-11-24
// === ONIONRING-WIDGET ===
//this file contains the code which builds the widget shown on each page in the ring. ctrl+f 'EDIT THIS' if you're looking to change the actual html of the widget
var tag = document.getElementById(ringID); //find the widget on the page
thisSite = window.location.href; //get the url of the site we're currently on
thisIndex = null;
// FIX
thisSite = thisSite.replace("alfieking.dev", "proot.uk"); // fix domain
thisSite = thisSite.replace("http:\/\/127.0.0.1:5000", "https:\/\/proot.uk"); // For debug purposes
// go through the site list to see if this site is on it and find its position
for (i = 0; i < sites.length; i++) {
if (thisSite.startsWith(sites[i])) { //we use startswith so this will match any subdirectory, users can put the widget on multiple pages
thisIndex = i;
break; //when we've found the site, we don't need to search any more, so stop the loop
}
}
function randomSite() {
otherSites = sites.slice(); //create a copy of the sites list
otherSites.splice(thisIndex, 1); //remove the current site so we don't just land on it again
randomIndex = Math.floor(Math.random() * otherSites.length);
location.href = otherSites[randomIndex];
}
//if we didn't find the site in the list, the widget displays a warning instead
if (thisIndex == null) {
tag.insertAdjacentHTML('afterbegin', `
<table>
<tr>
<td>This site isn't part of the ${ringName} webring yet 😿</td>
</tr>
</table>
`);
}
else {
//find the 'next' and 'previous' sites in the ring. this code looks complex
//because it's using a shorthand version of an if-else statement to make sure
//the first and last sites in the ring join together correctly
previousIndex = (thisIndex-1 < 0) ? sites.length-1 : thisIndex-1;
nextIndex = (thisIndex+1 >= sites.length) ? 0 : thisIndex+1;
indexText = ""
//if you've chosen to include an index, this builds the link to that
if (useIndex) {
indexText = `<a href='${indexPage}'>index</a> | `;
}
randomText = ""
//if you've chosen to include a random button, this builds the link that does that
if (useRandom) {
randomText = `<a href='javascript:void(0)' onclick='randomSite()'>random</a> | `;
}
//this is the code that displays the widget - EDIT THIS if you want to change the structure
tag.insertAdjacentHTML('afterbegin', `
<table>
<tr>
<td class='webring-prev'><a href='${sites[previousIndex]}'>prev</a></td>
<td class='webring-info'>This site is part of the ${ringName} webring 😸</br>
<span class='webring-links'>
${randomText}
${indexText}
<a href='https://garlic.garden/onionring/'>onionring</a></span></td>
<td class='webring-next'><a href='${sites[nextIndex]}'>next</a></td>
</tr>
</table>
`);
}

View File

@@ -1,106 +0,0 @@
const tips_of_the_day = [
`The Revolver deals <span style="color:var(--smileos2-emphasis)">locational damage</span>.<br>A <span style="color:var(--smileos2-emphasis)">headshot</span> deals <span style="color:var(--smileos2-emphasis)">2x</span> damage and a <span style="color:var(--smileos2-emphasis)">limbshot</span> deals <span style="color:var(--smileos2-emphasis)">1.5x</span> damage.`,
`<span style="color:var(--smileos2-emphasis)">Dash</span>: Fully invincible, costs stamina<br><span style="color:var(--smileos2-emphasis)">Slide</span>: Greater distance, no invincibility<br><span style="color:var(--smileos2-emphasis)">Jump</span>: Quickly out of melee range, less control`,
`<span style="color:var(--smileos2-emphasis)">Shotgun parries</span>: A <span style="color:var(--smileos2-emphasis)">point-blank</span> Shotgun shot to the torso right before an enemy attack lands will deal massive damage.`,
`<span style="color:var(--smileos2-emphasis)">Attack sound cues</span> allow you to keep track of enemies who are off screen.`,
`Some enemies make <span style="color:var(--smileos2-emphasis)">idle sounds</span> to make them easier to track.`,
`Use the <span style="color:cyan">ATTRACTOR NAILGUN</span>'s magnets to form concentrated <span style="color:var(--smileos2-emphasis)">orbs</span> of nails that can be <span style="color:var(--smileos2-emphasis)">moved</span> around using the pull force of <span style="color:var(--smileos2-emphasis)">other magnets</span>.`,
`Enemies <span style="color:var(--smileos2-emphasis)">can hurt</span> other enemy types. With quick thinking and positioning, powerful enemies can turn into powerful weapons.`,
`The <span style="color:cyan">ATTRACTOR NAILGUN</span>'s magnets can be attached to enemies to make mobile targets easy to hit with nails.`,
`Enemies <span style="color:var(--smileos2-emphasis)">scream</span> when falling from a <span style="color:var(--smileos2-emphasis)">fatal height</span>.`,
`<span style="color:var(--smileos2-emphasis)">SLAM BOUNCING</span>: Jump immediately after landing from a <span style="color:var(--smileos2-emphasis)">ground slam</span> to jump higher. The longer the ground slam fall, the higher the bounce.`,
`<span style="color:var(--smileos2-emphasis)">RAILCANNON</span> variations all share the same cooldown. Choose wisely which variation best fits the situation.`,
`<span style="color:var(--smileos2-emphasis)">SLIDING</span> will retain previous momentum for a short amount of time. Chaining quick <span style="color:var(--smileos2-emphasis)">SLIDE JUMPS</span> after a <span style="color:var(--smileos2-emphasis)">DASH JUMP</span> will give you incredible sustained speed.`,
`<span style="color:var(--smileos2-emphasis)">Environmental hazards</span> such as harmful liquids will hurt enemies as well.`,
`If you're having trouble keeping up with a tough enemy, <span style="color:var(--smileos2-emphasis)">stand back and observe</span>. Every enemy has its <span style="color:var(--smileos2-emphasis)">tells</span> and <span style="color:var(--smileos2-emphasis)">patterns</span> and learning those can be your key to victory.`,
`<span style="color:var(--smileos2-emphasis)">HITSCAN</span> weapons can be used to hit the shotgun's <span style="color:cyan">CORE EJECT</span> in mid-air to increase its damage and blast radius.`,
`<span style="color:var(--smileos2-emphasis)">POWER-UPS</span> can be stacked.`,
`Hitting an enemy with only the <span style="color:var(--smileos2-emphasis)">edge</span> of an <span style="color:var(--smileos2-emphasis)">explosion</span> will launch them without dealing much damage, making it a risky but effective tool against <span style="color:var(--smileos2-emphasis)">Stalkers</span>.`,
`Airborne <span style="color:var(--smileos2-emphasis)">coins</span> can be shot with any <span style="color:var(--smileos2-emphasis)">hitscan</span> weapon.`,
`Falling <span style="color:var(--smileos2-emphasis)">underwater</span> is slow, but a <span style="color:var(--smileos2-emphasis)">ground slam</span> allows for a quick return to the ground.`,
`<span style="color:var(--smileos2-emphasis)">Sliding</span> onto water will cause one to <span style="color:var(--smileos2-emphasis)">skip across</span> its surface.`,
`If an enemy is <span style="color:var(--smileos2-emphasis)">blessed</span> by an <span style="color:var(--smileos2-emphasis)">Idol</span>, a direct visible connection is formed between the two, allowing one to easily <span style="color:var(--smileos2-emphasis)">track down</span> and destroy the protector.`,
`<span style="color:var(--smileos2-emphasis)">Explosions</span> deflect <span style="color:var(--smileos2-emphasis)">projectiles</span>.<br><br>If an explosion is caused right from where an enemy shoots a projectile, it can <span style="color:var(--smileos2-emphasis)">backfire</span> and <span style="color:var(--smileos2-emphasis)">hit them</span> instead.`,
`The space of a <span style="color:var(--smileos2-emphasis)">large</span> arena can be used to one's advantage. Using the environment to <span style="color:var(--smileos2-emphasis)">break line-of-sight</span> with enemies allows for some breathing room and time to consider <span style="color:var(--smileos2-emphasis)">target prioritization</span>.`,
`<span style="color:var(--smileos2-emphasis)">DON'T</span> WASTE STAMINA! Dashing <span style="color:var(--smileos2-emphasis)">needlessly</span> while fighting very <span style="color:var(--smileos2-emphasis)">aggressive foes</span> will quickly cause one to have none left when it is most needed.`,
`<span style="color:var(--smileos2-emphasis)">Homing projectiles</span> may be more difficult to dodge, but their tracking and slower speed makes them much <span style="color:var(--smileos2-emphasis)">easier</span> to <span style="color:var(--smileos2-emphasis)">parry</span>.`,
`<span style="color:var(--smileos2-emphasis)">Mannequins</span> can be hard to hit due to their speed, but they lose air control when <span style="color:var(--smileos2-emphasis)">launched</span> or <span style="color:var(--smileos2-emphasis)">shot down</span> from a surface, making them <span style="color:var(--smileos2-emphasis)">unable to move</span> for a short moment.`,
`The <span style="color:red">Knuckleblaster</span>'s <span style="color:var(--smileos2-emphasis)">blast wave</span> is also capable of breaking a <span style="color:var(--smileos2-emphasis)">Gutterman's shield</span>, and is much easier to land in a chaotic scenario.`,
`A <span style="color:var(--smileos2-emphasis)">direct hit</span> from the <span style="color:red">Knuckleblaster</span> has extremely powerful <span style="color:var(--smileos2-emphasis)">knockback</span>, making it extremely powerful for launching enemies into <span style="color:var(--smileos2-emphasis)">pits</span> and other <span style="color:var(--smileos2-emphasis)">environmental hazards</span>.`,
`Didn't expect me, huh?`,
`<span style="color:#FF0078">Magenta</span> colored attacks can <span style="color:var(--smileos2-emphasis)">not</span> be dashed through and must be <span style="color:var(--smileos2-emphasis)">avoided entirely</span>.`,
`<span style="color:var(--smileos2-emphasis)">Blood Puppets</span> do not grant kills or style points, but their <span style="color:var(--smileos2-emphasis)">blood</span> can still <span style="color:var(--smileos2-emphasis)">heal</span>.`,
`When facing down <span style="color:var(--smileos2-emphasis)">a difficult foe</span>, it may be beneficial to first get rid of the <span style="color:var(--smileos2-emphasis)">fodder</span> to reduce distractions.`,
`Sometimes it may be more beneficial to <span style="color:var(--smileos2-emphasis)">stay at a distance</span> and wait for an opening <span style="color:var(--smileos2-emphasis)">before</span> getting close.`,
`If you're having <span style="color:var(--smileos2-emphasis)">trouble</span> with a specific encounter, take a moment to <span style="color:var(--smileos2-emphasis)">weigh your options</span>. <br> There may be some <span style="color:var(--smileos2-emphasis)">trick</span>, <span style="color:var(--smileos2-emphasis)">tool</span> or <span style="color:var(--smileos2-emphasis)">alternative prioritization</span> that will tip the scales in your favor.`,
`<span style="color:var(--smileos2-emphasis)">ENEMY STEP</span>: Jump while in mid-air <span style="color:var(--smileos2-emphasis)">near an enemy</span> to jump off the enemy. This resets the amount of available walljumps without needing to land.`,
`<span style="color:var(--smileos2-emphasis)">Parries</span> can be used as a powerful healing tool.<br><br>Parrying any enemy projectile or melee attack will <span style="color:red">fully replenish your health</span> up to the hard damage limit.`,
`H a v e &nbsp; f u n .`,
`If blown too far off the arena, <span style="color:lime">PUMP CHARGE</span>'s overcharge is a good way to get back.`,
`<span style="color:var(--smileos2-emphasis)">CHEATS</span> can be enabled in other levels by inputting <span style="color:var(--smileos2-emphasis)">🡡 🡡 🡣 🡣 🡠 🡢 🡠 🡢 B A</span> Enabling cheats will disable ranks`,
`You can pick up the cut weapons on the second floor.`
]
var click = new Audio('/static/content/smileos/SmileOS2Click.ogx');
document.getElementById("smileos-about").addEventListener("click", function() {
click.play();
document.getElementById("smileos-window-title").innerHTML = "About";
document.getElementById("smileos-window-content").innerHTML = `
<span class="red">SmileOS</span>: is the operating system found on most digital interfaces found throughout
<span class="red">Hell</span>. <span class="red">SmileOS 1.0</span> is employed on pannels despite its poor user and developer experience to conserve
blood after the war, while <span class="red">SmileOS 2.0</span> is used for terminals requireing higher blood consumption.
`;
});
document.getElementById("smileos-snake").addEventListener("click", function() {
click.play();
document.getElementById("smileos-window-title").innerHTML = "Snake";
document.getElementById("smileos-window-content").innerHTML = `
<img src="/static/content/smileos/KITR_Build.webp" alt="under construction">
`;
});
document.getElementById("smileos-leaderboard").addEventListener("click", function() {
click.play();
document.getElementById("smileos-window-title").innerHTML = "Leaderboard";
document.getElementById("smileos-window-content").innerHTML = `
<img src="/static/content/smileos/KITR_Build.webp" alt="under construction">
`;
});
document.getElementById("smileos-tip").addEventListener("click", function() {
click.play();
document.getElementById("smileos-window-title").innerHTML = "Tip of the Day";
document.getElementById("smileos-window-content").innerHTML = tips_of_the_day[Math.floor(Math.random() * tips_of_the_day.length)];
});
async function playAudio(url) {
return new Promise((resolve) => {
const audio = new Audio(url);
audio.addEventListener('ended', resolve); // Resolve the promise when audio ends
audio.volume = 0.4;
audio.play();
});
}
document.addEventListener("DOMContentLoaded", async function() {
document.getElementById("smileos-window-title").innerHTML = "Tip of the Day";
document.getElementById("smileos-window-content").innerHTML = tips_of_the_day[Math.floor(Math.random() * tips_of_the_day.length)];
await playAudio('/static/content/smileos/SmileOS2Startup.ogx');
var music = new Audio('/static/content/smileos/Shopmusic.ogx');
music.loop = true;
music.volume = 0.2;
music.play();
});
document.getElementById("smileos-restart-music").addEventListener("click", async function() {
await playAudio('/static/content/smileos/SmileOS2Startup.ogx');
var music = new Audio('/static/content/smileos/Shopmusic.ogx');
music.loop = true;
music.volume = 0.2;
music.play();
});

174
static/js/snake.js Normal file
View File

@@ -0,0 +1,174 @@
const canvas = document.getElementById('snakeCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 20;
const tileSize = 100;
const snakeSize = 60;
const foodSize = 80;
canvas.width = gridSize * tileSize;
canvas.height = gridSize * tileSize;
let snake = [{ x: 10, y: 10 }, { x: 10, y: 11 }, { x: 10, y: 12 }];
let direction = { x: 0, y: 0 };
let food = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) };
let score = 0;
let gameOver = false;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw grid of checkerboard pattern
for (let x = 0; x < gridSize; x++) {
for (let y = 0; y < gridSize; y++) {
ctx.fillStyle = (x + y) % 2 === 0 ?
getComputedStyle(document.documentElement).getPropertyValue('--background-color') :
getComputedStyle(document.documentElement).getPropertyValue('--secondary-background-color');
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
// Draw snake
snake.forEach(segment => {
let nextVec = { x: 0, y: 0 };
// if there is a segment after the current segment
if (snake.indexOf(segment) < snake.length - 1) {
const nextSegment = snake[snake.indexOf(segment) + 1];
nextVec.x = nextSegment.x - segment.x;
nextVec.y = nextSegment.y - segment.y;
}
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--primary-color');
if (nextVec.x === 0 && nextVec.y === 0) {
ctx.fillRect(
segment.x * tileSize + (tileSize - snakeSize) / 2,
segment.y * tileSize + (tileSize - snakeSize) / 2,
snakeSize,
snakeSize
);
} else if (nextVec.x > 0 || nextVec.y > 0) {
ctx.fillRect(
segment.x * tileSize + (tileSize - snakeSize) / 2,
segment.y * tileSize + (tileSize - snakeSize) / 2,
snakeSize + nextVec.x * (tileSize - snakeSize),
snakeSize + nextVec.y * (tileSize - snakeSize)
);
} else {
ctx.fillRect(
segment.x * tileSize + (tileSize - snakeSize) / 2 + nextVec.x * (tileSize - snakeSize),
segment.y * tileSize + (tileSize - snakeSize) / 2 + nextVec.y * (tileSize - snakeSize),
snakeSize + Math.abs(nextVec.x) * (tileSize - snakeSize),
snakeSize + Math.abs(nextVec.y) * (tileSize - snakeSize)
);
}
});
// Draw food
ctx.fillStyle = '#ff4d4d';
ctx.fillRect(
food.x * tileSize + (tileSize - foodSize) / 2,
food.y * tileSize + (tileSize - foodSize) / 2,
foodSize,
foodSize
);
}
function update() {
if (gameOver) return;
// Move snake
const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
// Add new head
snake.unshift(head);
// Check for food collision
if (head.x === food.x && head.y === food.y) {
score += 10; // Increase score
placeFood();
} else {
snake.pop(); // Remove tail if no food eaten
}
// Check for wall collision
if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {
gameOver = true;
return;
}
// Check for self collision
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
gameOver = true;
return;
}
}
}
function placeFood() {
do {
food.x = Math.floor(Math.random() * gridSize);
food.y = Math.floor(Math.random() * gridSize);
} while (snake.some(segment => segment.x === food.x && segment.y === food.y));
}
function changeDirection(event) {
switch (event.key) {
case 'w':
if (direction.y === 0) direction = { x: 0, y: -1 };
break;
case 's':
if (direction.y === 0) direction = { x: 0, y: 1 };
break;
case 'a':
if (direction.x === 0) direction = { x: -1, y: 0 };
break;
case 'd':
if (direction.x === 0) direction = { x: 1, y: 0 };
break;
}
}
// Menu to start the game
function menu() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--background-color');
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = 'center';
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-color');
ctx.font = '200px Arial';
ctx.fillText('Snake Game', canvas.width / 2, canvas.height / 2);
ctx.font = '100px Arial';
ctx.fillText('Press W/A/S/D to move', canvas.width / 2, canvas.height / 2 + 100);
ctx.fillText('Click to start', canvas.width / 2, canvas.height / 2 + 200);
canvas.addEventListener('click', startGame);
}
function gameLoop() {
if (!gameOver) {
update();
draw();
setTimeout(gameLoop, 100);
} else {
document.removeEventListener('keydown', changeDirection);
document.getElementById('score').value = score;
alert(`Game Over! Your score: ${score}`);
menu();
}
}
function startGame() {
snake = [{ x: 10, y: 10 }, { x: 10, y: 11 }, { x: 10, y: 12 }];
direction = { x: 1, y: 0 };
food = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) };
score = 0;
gameOver = false;
canvas.removeEventListener('click', startGame);
document.addEventListener('keydown', changeDirection);
gameLoop();
}
menu();

View File

@@ -1,78 +0,0 @@
//YELLOW TERMINAL
// onionring.js is made up of four files - onionring-widget.js (this one!), onionring-index.js, onionring-variables.js and onionring.css
// it's licensed under the cooperative non-violent license (CNPL) v4+ (https://thufie.lain.haus/NPL.html)
// it was originally made by joey + mord of allium (蒜) house, last updated 2020-11-24
// === ONIONRING-WIDGET ===
//this file contains the code which builds the widget shown on each page in the ring. ctrl+f 'EDIT THIS' if you're looking to change the actual html of the widget
var tag = document.getElementById(ringID); //find the widget on the page
thisSite = window.location.href; //get the url of the site we're currently on
thisIndex = null;
// FIX
thisSite = thisSite.replace("alfieking.dev", "proot.uk"); // fix domain
thisSite = thisSite.replace("http:\/\/127.0.0.1:5000", "https:\/\/proot.uk"); // For debug purposes
// go through the site list to see if this site is on it and find its position
for (i = 0; i < sites.length; i++) {
if (thisSite.startsWith(sites[i])) { //we use startswith so this will match any subdirectory, users can put the widget on multiple pages
thisIndex = i;
break; //when we've found the site, we don't need to search any more, so stop the loop
}
}
function randomUltraRingSite() {
otherSites = sites.slice(); //create a copy of the sites list
otherSites.splice(thisIndex, 1); //remove the current site so we don't just land on it again
randomIndex = Math.floor(Math.random() * otherSites.length);
location.href = otherSites[randomIndex];
}
//if we didn't find the site in the list, the widget displays a warning instead
if (thisIndex == null) {
tag.insertAdjacentHTML('afterbegin', `
<table>
<tr>
<td>This site isn't part of <a href="https://jack-dawlia.neocities.org/page/shrines/ultrakill/ULTRARING" target='_parent'>${ringName}</a> yet!</td>
</tr>
</table>
`);
}
else {
//find the 'next' and 'previous' sites in the ring. this code looks complex
//because it's using a shorthand version of an if-else statement to make sure
//the first and last sites in the ring join together correctly
previousIndex = (thisIndex-1 < 0) ? sites.length-1 : thisIndex-1;
nextIndex = (thisIndex+1 >= sites.length) ? 0 : thisIndex+1;
indexText = ""
//if you've chosen to include an index, this builds the link to that
if (useIndex) {
indexText = `<a href='${indexPage}' class="textshadow" target='_parent'>[INDEX]</a> | `;
}
randomText = ""
//if you've chosen to include a random button, this builds the link that does that
if (useRandom) {
randomText = `<a href='javascript:void(0)' onclick='randomUltraRingSite()' style="color: red;" class="textshadow" target='_parent'>[RANDOM]</a>`;
}
//this is the code that displays the widget - EDIT THIS if you want to change the structure
tag.insertAdjacentHTML('afterbegin', `
<table>
<tr>
<td class='webring-prev'><a href='${sites[previousIndex]}' style="color: red;" class="textshadow" title="[PREV]" target='_parent'><<</a></td>
<td class='webring-info'><a href="https://jack-dawlia.neocities.org/page/shrines/ultrakill/ULTRARING" style="color: red;" target='_parent'><img src="https://jack-dawlia.neocities.org/image/ultraring-yellow-terminal.png" alt="This site is part of ULTRARING" title="ULTRARING" style="max-width:100%"></a></br>
<span class='webring-links'>
${randomText}
${indexText}
<td class='webring-next'><a href='${sites[nextIndex]}' style="color: red;" title="[NEXT]" target='_parent'>>></a></td>
</tr>
</table>
`);
}

View File

@@ -3,19 +3,19 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ name }}'s basement{% endblock %}</title>
<link rel="icon" href="{% block icon %}/static/content/{% if name == 'Toaster' %}toaster/Toaster_v1.0_sticker.png{% else %}general_images/icon.webp{% endif %}{% endblock %}">
<link rel="stylesheet" href="/static/css/bases/base.css">
<title>{% block title %}Alfie's basement{% endblock %}</title>
<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="{% 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="{% if name == 'Toaster' %}proot.uk{% else %}alfieking.dev{% endif %}">
<meta property="og:url" content="https://{% if name == 'Toaster' %}proot.uk{% else %}alfieking.dev{% endif %}/">
<meta property="og:title" content="{{ self.title() }}">
<meta property="og:description" content="{{ self.description() }}">
<meta property="og:image" content="{% block og_image %}/static/content/{% if name == 'Toaster' %}toaster/Toaster_v1.0_sticker.png{% else %}general_images/icon.webp{% endif %}{% endblock %}">
<meta property="og:site_name" content="Alfieking.dev">
<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,7 +28,6 @@
<ul>
<li><a href="/">Home</a></li>
<li><a href="/toaster">Toaster</a></li>
<li><a href="/terminal" class="ultrafont">Terminal</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>
@@ -42,56 +41,24 @@
</section>
</nav>
<section>
<h6 class="irken">heya, try typing "furry", "irken" or "<span class="scratch">scratch</span>" into this page!</h6>
<h6 class="irken">heya, try typing "furry" and "irken" into this page!</h6>
</section>
<section id="buttons">
<h1>BUTTONS</h1>
<ul>
<li><a herf="https://hijpixel.nekoweb.org/"><img src="/static/content/buttons/hijpixel.gif" alt="hijpixel"></a></li>
<li><a href="https://lensdeer.neocities.org/"><img src="/static/content/buttons/lensdeer.gif" alt="lensdeer"></a></li>
<li><a href="https://emmixis.net/"><img src="/static/content/buttons/emmixis.gif" alt="emmixis"></a></li>
<li><a href="https://dimden.dev/"><img src="https://dimden.dev/services/images/88x31.gif" alt="dimden"></a></li><!-- hotlink on purpose -->
<li><a href="https://ne0nbandit.neocities.org/"><img src="/static/content/buttons/ne0nbandit.png" alt="ne0nbandit"></a></li>
<li><a href="https://thinliquid.dev"><img src="/static/content/buttons/thnlqd.png" alt="thinliquid"></a></li>
<li><a href="https://nekoweb.org/"><img src="/static/content/buttons/nekoweb.gif" alt="nekoweb"></a><!-- button by s1nez.nekoweb.org --></li>
<li><a href="https://s1nez.nekoweb.org/"><img src="/static/content/buttons/s1nez.gif" alt="s1nez"></a></li>
<li><a href="https://beeps.website"><img src="/static/content/buttons/beeps.gif" alt="beeps"></a></li>
<li><a href="https://itsnotstupid.com"><img src="/static/content/buttons/insia.gif" alt="itsnotstupid"></a></li>
<li><a href='https://blinkies.cafe'><img src='/static/content/buttons/blinkiescafe.gif' alt='blinkies.cafe | make your own blinkies!'></a></li>
<li><a href="https://eightyeightthirty.one"><img src="/static/content/buttons/8831.png" alt="88x31"></a></li>
<li><a href="https://neocities.org"><img src="/static/content/buttons/neocities.gif" alt="neocities"></a></li>
<li><a href="https://tuxedodragon.art"><img src="/static/content/buttons/tuxedodragon.gif" alt="tuxedodragon"></a></li>
<li><a href="https://beepi.ng"><img src="https://beepi.ng/88x31.png" width="88" height="31" alt="unnick"></a></li>
<li><a href="https://sneexy.synth.download"><img src="https://synth.download/assets/buttons/sneexy.svg" alt="Sneexy"></a></li>
<li><a href="https://kraafter.me/"><img src="https://kraafter.me/assets/img/button.png" alt="Kraafter.me button" title="kraaftersite"></a></li>
<li><a href="https://dimden.dev/"><img src="https://dimden.dev/services/images/88x31.gif" alt="dimden"></a></li>
<li><a href="https://ne0nbandit.neocities.org/"><img src="https://ne0nbandit.github.io/assets/img/btn/mine/nbbanner.png" alt="ne0nbandit"></a></li>
<li><a href="https://thinliquid.dev"><img src="https://thinliquid.dev/thnlqd.png" alt="thinliquid"></a></li>
<li><a href="https://nekoweb.org/"><img src="https://nekoweb.org/assets/buttons/button6.gif" alt="nekoweb"></a><!-- button by s1nez.nekoweb.org --></li>
<li><a href="https://s1nez.nekoweb.org/"><img src="https://s1nez.nekoweb.org/BUTTON.gif" alt="s1nez"></a></li>
<li><a href="https://beeps.website"><img src="https://beeps.website/assets/images/88x31-d.gif" alt="beeps"></a></li>
<li><a href="https://itsnotstupid.com"><img src="https://itsnotstupid.com/pics/button1.gif" alt="itsnotstupid"></a></li>
<li><a href='https://blinkies.cafe'><img src='https://blinkies.cafe/b/display/blinkiesCafe-badge.gif' alt='blinkies.cafe | make your own blinkies!'></a></li>
<li><a href="https://eightyeightthirty.one"><img src="https://eightyeightthirty.one/88x31.png" alt="88x31"></a></li>
<li><a href="https://neocities.org"><img src="https://cyber.dabamos.de/88x31/neocities-now.gif" alt="neocities"></a></li>
<li><a href="https://tuxedodragon.art"><img src="https://tuxedodragon.art/tuxedodragon%2088x31.gif" alt="tuxedodragon"></a></li>
</ul>
</section>
<section>
<div id='furnix'>
<script type="text/javascript" src="https://tapeykatt.neocities.org/furnix/onionring-variables.js"></script>
<script type="text/javascript" src="/static/js/furnix_widget.js"></script>
</div>
</section>
<section class="webring">
<a href="https://stellophiliac.github.io/roboring">Roboring</a><br>
<a href="https://stellophiliac.github.io/roboring/Toaster/previous"><--</a>
<a href="https://stellophiliac.github.io/roboring/Toaster/next">--></a>
</section>
<section>
<div id='ultraring'>
<script type="text/javascript" src="https://jack-dawlia.neocities.org/page/shrines/ultrakill/ULTRARING/onionring-variables.js"></script>
<script type="text/javascript" src="/static/js/ultraring_widget.js"></script>
<noscript>
This site is part of <a href="https://jack-dawlia.neocities.org/page/shrines/ultrakill/ULTRARING">ULTRARING</a>!
</noscript>
</div>
</section>
<section class="webring">
<a href="https://keithhacks.cyou/furryring.php">Furryring</a><br>
<a href="https://keithhacks.cyou/furryring.php?prev=proot.uk"><--</a>
<a href="https://keithhacks.cyou/furryring.php?next=proot.uk">--></a>
</section>
<iframe width="150" height="450" style="border:none" src="https://dogspit.nekoweb.org/sidelink.html" name="sidelink"></iframe>
<section>
<pre class="vsmoltext"> |\ _,,,---,,_<br>ZZZzz /,`.-'`' -. ;-;;,_<br> |,4- ) )-,_. ,\ ( `'-'<br> '---''(_/--' `-'\_)</pre>
</section>
@@ -100,15 +67,12 @@
<main id="main">
<header id="home">
<div class="row">
<img src="/static/content/{% if name == 'Toaster' %}toaster/Toaster_v1.0_sticker.png{% else %}general_images/icon.webp{% endif %}">
<img src="/static/content/general_images/icon.webp">
<div>
<h1>{{ name }}</h1>
<h1>Alfie King</h1>
<h2 id="typing">server backend survivor</h2>
</div>
</div>
<a href="/toaster" id="toaster-wave">
<img src="/static/content/toaster/Toaster_v1.1.png" alt="toaster">
</a>
</header>
<nav id="alt-nav">
<ul>

View File

@@ -1,10 +1,10 @@
{% extends "bases/base.html" %}
{% extends "base.html" %}
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
{% block description %}server backend survivor{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/bases/directory.css">
<link rel="stylesheet" href="/static/css/directory.css">
{% endblock %}
{% block scripts %}

View File

@@ -1,10 +0,0 @@
{% extends "bases/base.html" %}
{% block title %}{{ title }} - Alfie's basement{% endblock %}
{% block description %}server backend survivor{% endblock %}
{% block content %}
<section>
{{ markdown|safe }}
</section>
{% endblock %}

View File

@@ -4,7 +4,7 @@
{% block description %}Bad request. The server could not understand the request due to invalid syntax.{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/errors/400.css">
<link rel="stylesheet" href="/static/css/400.css">
{% endblock %}
{% block content %}

View File

@@ -7,13 +7,14 @@
<section>
<h1>404</h1>
<p>
It seems like the thing you are looking for does not exist or <code>rm -rf</code> itself out of exsistance.
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>Actual error</h2>
<h2>The actual error for the 2 ppl who care</h2>
<p>
{{ error }}
404: {{ requested_url }} not found :3
</p>
</section>
{% endblock %}
</section>

View File

@@ -4,7 +4,7 @@
{% block description %}An unexpected error occurred on the server.{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/errors/500.css">
<link rel="stylesheet" href="/static/css/500.css">
{% endblock %}
{% block content %}
@@ -14,10 +14,4 @@
Oopsie Woopsie! Uwu We made a fucky wucky!! A wittle fucko boingo! The code monkeys at our headquarters are working VEWY HAWD to fix this!
</p>
</section>
<section>
<h2>Actual error</h2>
<p>
{{ error }}
</p>
</section>
{% endblock %}

View File

@@ -1,16 +0,0 @@
{% extends "bases/base.html" %}
{% block title %}{{ code }} - {{ err_name }}{% endblock %}
{% block description %}The page you are looking for does not exist.{% endblock %}
{% block content %}
<section>
<img src="https://http.cat/images/{{ code }}.jpg" alt="">
</section>
<section>
<h2>Actual error</h2>
<p>
{{ description }}
</p>
</section>
{% endblock %}

View File

@@ -1,7 +1,10 @@
{% extends "bases/base.html" %}
{% block title %}Home - {{ name }}'s basement{% endblock %}
{% 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">
@@ -11,8 +14,7 @@
<section>
<h1>A lil bit abt me</h1>
<p>
<span style="color: yellow; font-size: 0.8rem;">This is a "bit" out of date, will update soon ^^</span><br>
Im not good with writing so dont expect much here. I was a student learning c++ and python. I've Done a few projects that i think
Im not good with writing so dont expect much here. I am a student who is learning c++ and python. I've Done a few projects that i think
are decent enough to show off, so I have put them on this website. I like to mess around with linux and have a few servers that I run. I've
been running a server for a few years now, and I have learned a lot from it. I have also switched to linux on my main computer, which has been
slightly annoying at times (mainly because one of my most played games' anticheat doesn't support on linux atm. Also, the lack of photoshop is
@@ -38,7 +40,7 @@
<img src="https://s1nez.nekoweb.org/img/7dcd20d4.gif" alt="">
</section>
<div class="flex-row">
<a href="https://www.last.fm/user/acetheking987" id="spotify-link">
<a href="" id="spotify-link">
<div id="spotify">
<h1 id="spotify-title"></h1>
<h2 id="spotify-artist"></h2>
@@ -63,17 +65,12 @@
<img src="https://adriansblinkiecollection.neocities.org/stamps/e43.gif" alt="">
</section>
</div>
<section class="smileos">
<span style="margin: auto;">
Try going to the <span style="color: #ff4343;">Terminal</span> for more information
</span>
</section>
<section>
<h1>Projects & stuff</h1>
<p>just some projects ive worked on over time</p>
<ul>
<li>
<h2>alfieking.dev/proot.uk</h2>
<h2>alfieking.dev</h2>
<p>
This website is a project that I have been working on for a while now. I have made a few versions of it, but I have
never been happy with them. I am quite happy with this version atm since it is more organized and has a design that I
@@ -110,24 +107,6 @@
<h1>Some News</h1>
<h6>(dont expect this to be updated often tho :P)</h6>
<ul>
<li>
<h2>05-03-2026</h2>
<p>
This is a short post since i plan on reworking these in the next few days, but i had mad a lot of changes to the site. Some are visible and some are not;
the bigget change in the new terminal page that needs more features but that is a later me issue. I also redid the backend so making updates are much
easier now. I also need to update the main about me and projects sections since they are old and not very good. """hopefully""" i should make a new news post
soon when i have reworked the system.
</p>
</li>
<li>
<h2>18-01-2026</h2>
<p>
:O an update! thats unheard of on this site (aleast its more often than tf2 gets updates). finding motivation to work on things has been painful
recently, but im wokring on my mental state a bit so hopefully there will be more updates. I am writing this before i make any major changes but
i hope to add a blog or something, or maybe a daily thoughts thing that pings my phone to get me to write something. I also need to rewrite most
of the home page as well since its kinda out of date :P
</p>
</li>
<li>
<h2>28-06-2025</h2>
<p>
@@ -135,7 +114,7 @@
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.
that I find interesting.
</p>
</li>
<li>
@@ -150,5 +129,4 @@
</li>
</ul>
</section>
<iframe src="https://john.citrons.xyz/embed?ref=alfieking.dev" style="margin-left:auto;display:block;margin-right:auto;max-width:732px;width:100%;height:94px;border:none;"></iframe>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "bases/base.html" %}
{% block title %}Critters MK - {{ name }}'s basement{% endblock %}
{% 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 %}
@@ -34,22 +34,12 @@ protogen v1.0, toaster v1.0
<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">
<h2 class="gallery-date">26th July 2025</h2>
<div class="gallery-images">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_152110445.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155134418.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155226274.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
</div>
</div>
<div class="gallery">
<h2 class="gallery-date">23rd Aug 2025</h2>
<div class="gallery-images">
<img src="/static/content/photos/fur_meets/23-08-2025_critters_mk/PXL_20250823_130640362.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/23-08-2025_critters_mk/PXL_20250823_130648109.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/23-08-2025_critters_mk/PXL_20250823_130659800.jpg" alt="Critters MK">
</div>
<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

@@ -1,6 +1,6 @@
{% extends "bases/base.html" %}
{% block title %}Paws'N'Pistons - {{ name }}'s basement{% endblock %}
{% 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 %}
@@ -42,25 +42,23 @@ protogen v1.0, toaster v1.0
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/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_200906329.jpg" alt="me in a fursuit" id="woooooo">
<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">
<h2 class="gallery-date">3rd Aug 2025</h2>
<div class="gallery-images">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141943558.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150138054.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150249916.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_183614897.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_140629639.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141242090.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_182023562.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_184321576.jpg" alt="Paws'N'Pistons">
</div>
<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

@@ -1,6 +1,6 @@
{% extends "bases/base.html" %}
{% block title %}Toaster - {{ name }}'s basement{% endblock %}
{% 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 %}
@@ -86,19 +86,19 @@ protogen v1.0, toaster v1.0
<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/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_152110445.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155134418.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155226274.jpg" alt="Critters MK">
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
<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/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_141943558.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150138054.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_150249916.jpg" alt="Paws'N'Pistons">
<img src="/static/content/photos/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_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>

View File

@@ -1,63 +0,0 @@
{% extends "bases/base.html" %}
{% block title %}SmileOS 2.0{% endblock %}
{% block description %}SmileOS 2.0{% endblock %}
{% block og_image %}static/content/smileos/SmileOS_2_icon_smile.webp{% endblock %}
{% block icon %}static/content/smileos/SmileOS_2_icon_smile.webp{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/terminal.css">
{% endblock %}
{% block content %}
<section id="terminal">
<div class="smileos-header">
<img src="/static/content/smileos/SmileOS_2_icon_smile.webp" alt="smile">
SmileOS 2.0
<img src="/static/content/smileos/SmileOS_2_top_button_5.png" alt="minimize" style="margin-left: auto;">
<img src="/static/content/smileos/SmileOS_2_top_button_4.png" alt="maximize">
<img src="/static/content/smileos/SmileOS_2_top_button_3.png" alt="close">
</div>
<div id="terminal-container">
<img src="/static/content/smileos/SmileOS2.webp" alt="logo" id="smileos-logo">
<div id="terminal-buttons">
<button class="terminal-button" id="smileos-tip">
Tip of the Day
</button>
<button class="terminal-button" id="smileos-snake">
Snake
</button>
<button class="terminal-button" id="smileos-leaderboard">
Leaderboard
</button>
<button class="terminal-button" id="smileos-about">
About
</button>
</div>
<div id="terminal-window">
<div class="smileos-header">
<img src="/static/content/smileos/SmileOS_2_icon_tip.webp" alt="tip">
<span id="smileos-window-title">Tip of the Day</span>
<img src="/static/content/smileos/SmileOS_2_top_button_5.png" alt="minimize" style="margin-left: auto;">
<img src="/static/content/smileos/SmileOS_2_top_button_4.png" alt="maximize">
<img src="/static/content/smileos/SmileOS_2_top_button_3.png" alt="close">
</div>
<div id="window-container">
<div id="window-content">
<span id="smileos-window-content"><span class="red">SLAM BOUNCING</span>: Jump immediately after landing from a <span class="red">ground slam</span> to jump higher. The longer the ground slam fall, the higher the bounce.</span>
</div>
</div>
</div>
</div>
</section>
<section>
<p>
Note: this page has background music, you may have to allow the music in the browser and refesh to let it play, or you can try
press <a id="smileos-restart-music">this</a> to restart it
</p>
</section>
{% endblock %}
{% block scripts %}
<script src="/static/js/smileos.js"></script>
{% endblock %}

1
thirdparty/inja vendored Submodule

Submodule thirdparty/inja added at 593ff96024

1
thirdparty/nlohmann vendored Submodule

Submodule thirdparty/nlohmann added at 000db7a6a2