major update
@@ -1,33 +1,8 @@
|
|||||||
name: Build and push container image
|
name: Deploy website
|
||||||
run-name: ${{ gitea.actor }} is building and pushing container image
|
run-name: ${{ gitea.actor }} is deploying update
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
|
||||||
GITEA_DOMAIN: git.alfieking.dev
|
|
||||||
GITEA_REGISTRY_USER: acetheking987
|
|
||||||
RESULT_IMAGE_NAME: acetheking987/alfieking.dev
|
|
||||||
|
|
||||||
jobs:
|
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
|
|
||||||
6
.gitignore
vendored
@@ -1,7 +1,3 @@
|
|||||||
.venv
|
|
||||||
.env
|
.env
|
||||||
flask_session
|
|
||||||
__pycache__
|
__pycache__
|
||||||
.vscode
|
.venv
|
||||||
db
|
|
||||||
app.log
|
|
||||||
22
dockerfile
@@ -1,22 +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
|
|
||||||
|
|
||||||
# run the application
|
|
||||||
ENTRYPOINT [ "gunicorn", "-b", ":5000", "--access-logfile", "-", "--error-logfile", "-", "src.wsgi:app" ]
|
|
||||||
5
run.sh
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
[ ! -f .env ] || export $(grep -v '^#' .env | xargs)
|
|
||||||
|
|
||||||
flask --app src.wsgi.py --debug run
|
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
# Imports
|
# Imports
|
||||||
from flask import Blueprint, render_template, abort
|
from flask import Blueprint, render_template, abort
|
||||||
from os import getenv as env
|
import os, markdown
|
||||||
import logging, os, markdown
|
|
||||||
|
|
||||||
# Create blueprint
|
# Create blueprint
|
||||||
bp = Blueprint(
|
bp = Blueprint('dynamic_routes', __name__)
|
||||||
'dynamic_routes',
|
template_folder = "templates"
|
||||||
__name__,
|
|
||||||
template_folder=env('TEMPLATE_FOLDER', default='templates'),
|
|
||||||
static_folder=env('STATIC_FOLDER', default='../static')
|
# helper func
|
||||||
)
|
def get_path(file) -> str:
|
||||||
|
return os.path.join(template_folder, "pages", file)
|
||||||
|
|
||||||
# Create logger
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Get all files in folder
|
# Get all files in folder
|
||||||
def ListFiles(path):
|
def ListFiles(path):
|
||||||
path = os.path.join(bp.template_folder, 'pages', path)
|
path = get_path(path)
|
||||||
files = []
|
files = []
|
||||||
for root, dirs, files_in_dir in os.walk(path):
|
for root, dirs, files_in_dir in os.walk(path):
|
||||||
for file in files_in_dir:
|
for file in files_in_dir:
|
||||||
@@ -25,11 +24,12 @@ def ListFiles(path):
|
|||||||
files.append(os.path.relpath(os.path.join(root, dir), path) + '/')
|
files.append(os.path.relpath(os.path.join(root, dir), path) + '/')
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
# Catch-all route for generic pages
|
# Catch-all route for generic pages
|
||||||
@bp.route('/<path:filename>')
|
@bp.route('/<path:filename>')
|
||||||
def catch_all(filename):
|
def catch_all(filename):
|
||||||
if os.path.exists(os.path.join(bp.template_folder, 'pages', filename)):
|
if os.path.exists(get_path(filename)):
|
||||||
if os.path.isdir(os.path.join(bp.template_folder, 'pages', filename)):
|
if os.path.isdir(get_path(filename)):
|
||||||
return render_template(
|
return render_template(
|
||||||
'bases/directory.html',
|
'bases/directory.html',
|
||||||
directory=filename + "/" if not filename.endswith('/') else filename,
|
directory=filename + "/" if not filename.endswith('/') else filename,
|
||||||
@@ -38,11 +38,11 @@ def catch_all(filename):
|
|||||||
|
|
||||||
return render_template(f'pages/{filename}')
|
return render_template(f'pages/{filename}')
|
||||||
|
|
||||||
elif os.path.exists(os.path.join(bp.template_folder, 'pages', filename + '.html')):
|
elif os.path.exists(get_path(filename + '.html')):
|
||||||
return render_template(f'pages/{filename}.html')
|
return render_template(f'pages/{filename}.html')
|
||||||
|
|
||||||
elif os.path.exists(os.path.join(bp.template_folder, 'pages', filename + '.md')):
|
elif os.path.exists(get_path(filename + '.md')):
|
||||||
output = markdown.markdown(open(os.path.join(bp.template_folder, 'pages', filename + '.md'), "r").read())
|
output = markdown.markdown(open(get_path(filename + '.md'), "r").read())
|
||||||
return render_template(
|
return render_template(
|
||||||
f'bases/md.html',
|
f'bases/md.html',
|
||||||
title = filename.split("/")[-1],
|
title = filename.split("/")[-1],
|
||||||
39
src/errors.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from flask import Blueprint, render_template
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
from os import getenv as env
|
||||||
|
|
||||||
|
|
||||||
|
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), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/404')
|
||||||
|
@bp.app_errorhandler(404)
|
||||||
|
def not_found(error:HTTPException=None):
|
||||||
|
return render_template('errors/404.html', error=error), 404
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/400')
|
||||||
|
@bp.app_errorhandler(400)
|
||||||
|
def bad_request(error:HTTPException=None):
|
||||||
|
return render_template('errors/400.html', error=error), 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,
|
||||||
|
name = error.name
|
||||||
|
), error.code
|
||||||
74
src/main.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# IMPORTS
|
||||||
|
from flask import Flask, render_template
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
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
|
||||||
|
except ImportError:
|
||||||
|
import dynamic_routes, errors, pg_log
|
||||||
|
|
||||||
|
|
||||||
|
# ENV
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# BLUEPRINTS
|
||||||
|
app.register_blueprint(errors.bp, url_prefix="/errors")
|
||||||
|
app.register_blueprint(dynamic_routes.bp, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
|
# ROUTES
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.route("/toaster")
|
||||||
|
def toaster():
|
||||||
|
return render_template("toaster.html")
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
35
src/pg_log.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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()
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Imports
|
|
||||||
from flask import Blueprint, render_template
|
|
||||||
from werkzeug.exceptions import HTTPException
|
|
||||||
from os import getenv as env
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Create blueprint
|
|
||||||
bp = Blueprint(
|
|
||||||
'error_handlers',
|
|
||||||
__name__,
|
|
||||||
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
|
|
||||||
static_folder=env('STATIC_FOLDER', default='../static')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create logger
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Route for 500 error
|
|
||||||
@bp.route('/500')
|
|
||||||
@bp.app_errorhandler(500)
|
|
||||||
def internal_server_error(error:HTTPException=None):
|
|
||||||
return render_template('errors/500.html', error=error), 500
|
|
||||||
|
|
||||||
# Route for 404 error
|
|
||||||
@bp.route('/404')
|
|
||||||
@bp.app_errorhandler(404)
|
|
||||||
def not_found(error:HTTPException=None):
|
|
||||||
return render_template('errors/404.html', error=error), 404
|
|
||||||
|
|
||||||
# Route for 400 error
|
|
||||||
@bp.route('/400')
|
|
||||||
@bp.app_errorhandler(400)
|
|
||||||
def bad_request(error:HTTPException=None):
|
|
||||||
return render_template('errors/400.html', error=error), 400
|
|
||||||
|
|
||||||
# Route for all other errors
|
|
||||||
@bp.route('/idk')
|
|
||||||
@bp.app_errorhandler(Exception)
|
|
||||||
def nah(error:HTTPException=None):
|
|
||||||
if isinstance(error, HTTPException):
|
|
||||||
return render_template(
|
|
||||||
'errors/error.html',
|
|
||||||
code = error.code,
|
|
||||||
description = error.description,
|
|
||||||
name = error.name
|
|
||||||
), error.code
|
|
||||||
return render_template(
|
|
||||||
'errors/error.html',
|
|
||||||
code=418,
|
|
||||||
description="I honestly dont know",
|
|
||||||
name="I'm a teapot"
|
|
||||||
), 418
|
|
||||||
63
src/wsgi.py
@@ -1,63 +0,0 @@
|
|||||||
# Imports
|
|
||||||
from flask import Flask, render_template, send_file
|
|
||||||
from flask_session import Session
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from os import getenv as env
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import src.routes.error_handlers
|
|
||||||
import src.routes.dynamic_routes
|
|
||||||
|
|
||||||
# Load env
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# Create logger
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
|
||||||
stream_handler.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
file_handler = logging.FileHandler(filename='app.log')
|
|
||||||
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
|
||||||
file_handler.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# Add handlers to the logger
|
|
||||||
log = logging.getLogger()
|
|
||||||
log.setLevel(logging.DEBUG)
|
|
||||||
log.addHandler(stream_handler)
|
|
||||||
log.addHandler(file_handler)
|
|
||||||
log.info("Logging initialized")
|
|
||||||
|
|
||||||
# Create flask app
|
|
||||||
app = Flask(
|
|
||||||
__name__,
|
|
||||||
template_folder=env('TEMPLATE_FOLDER', default='../templates'),
|
|
||||||
static_folder=env('STATIC_FOLDER', default='../static')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Configure sessions
|
|
||||||
app.config["SESSION_PERMANENT"] = True
|
|
||||||
app.config["SESSION_TYPE"] = "filesystem"
|
|
||||||
Session(app)
|
|
||||||
|
|
||||||
# Load routes
|
|
||||||
app.register_blueprint(src.routes.error_handlers.bp, url_prefix='/error')
|
|
||||||
app.register_blueprint(src.routes.dynamic_routes.bp, url_prefix='/')
|
|
||||||
|
|
||||||
# Generic routes
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return render_template('index.html')
|
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
|
||||||
def favicon():
|
|
||||||
return send_file('../static/content/other/favicon.ico')
|
|
||||||
|
|
||||||
@app.route('/robots.txt')
|
|
||||||
def robots():
|
|
||||||
return send_file('../static/content/other/robots.txt')
|
|
||||||
|
|
||||||
# Route for sitemap.xml
|
|
||||||
@app.route('/sitemap.xml')
|
|
||||||
def sitemap():
|
|
||||||
return send_file('../static/content/other/sitemap.xml')
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
Disallow: /404
|
Disallow: /errors
|
||||||
Disallow: /500
|
|
||||||
Disallow: /400
|
|
||||||
|
|
||||||
Sitemap: https://alfieking.dev/sitemap.xml
|
Sitemap: https://alfieking.dev/sitemap.xml
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,4 @@
|
|||||||
<url>
|
<url>
|
||||||
<loc>https://alfieking.dev/toaster</loc>
|
<loc>https://alfieking.dev/toaster</loc>
|
||||||
</url>
|
</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>
|
</urlset>
|
||||||
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 4.6 MiB After Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 4.6 MiB After Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 3.5 MiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 3.5 MiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 4.4 MiB After Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 4.9 MiB After Width: | Height: | Size: 4.9 MiB |
|
Before Width: | Height: | Size: 5.1 MiB After Width: | Height: | Size: 5.1 MiB |
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 4.5 MiB After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 4.7 MiB |
@@ -41,7 +41,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</nav>
|
</nav>
|
||||||
<section>
|
<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", "irken" or "<span class="scratch">scratch</span>" into this page!</h6>
|
||||||
</section>
|
</section>
|
||||||
<section id="buttons">
|
<section id="buttons">
|
||||||
<h1>BUTTONS</h1>
|
<h1>BUTTONS</h1>
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ protogen v1.0, toaster v1.0
|
|||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<h2 class="gallery-date">26th July 2025</h2>
|
<h2 class="gallery-date">26th July 2025</h2>
|
||||||
<div class="gallery-images">
|
<div class="gallery-images">
|
||||||
<img src="/static/content/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_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/photos/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/photos/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">
|
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<h2 class="gallery-date">23rd Aug 2025</h2>
|
<h2 class="gallery-date">23rd Aug 2025</h2>
|
||||||
<div class="gallery-images">
|
<div class="gallery-images">
|
||||||
<img src="/static/content/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_130640362.jpg" alt="Critters MK">
|
||||||
<img src="/static/content/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_130648109.jpg" alt="Critters MK">
|
||||||
<img src="/static/content/fur_meets/23-08-2025_critters_mk/PXL_20250823_130659800.jpg" alt="Critters MK">
|
<img src="/static/content/photos/fur_meets/23-08-2025_critters_mk/PXL_20250823_130659800.jpg" alt="Critters MK">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ 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
|
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).
|
already planning on getting one, but this just made me want one more).
|
||||||
<br>
|
<br>
|
||||||
<img src="/static/content/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_200906329.jpg" alt="me in a fursuit" id="woooooo">
|
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_200906329.jpg" alt="me in a fursuit" id="woooooo">
|
||||||
<br>
|
<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^.
|
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>
|
<h1>Photos :3</h1>
|
||||||
@@ -52,14 +52,14 @@ protogen v1.0, toaster v1.0
|
|||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<h2 class="gallery-date">3rd Aug 2025</h2>
|
<h2 class="gallery-date">3rd Aug 2025</h2>
|
||||||
<div class="gallery-images">
|
<div class="gallery-images">
|
||||||
<img src="/static/content/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_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/photos/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/photos/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/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_140629639.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/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_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/photos/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">
|
<img src="/static/content/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_184321576.jpg" alt="Paws'N'Pistons">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# hello
|
|
||||||
## this is a test
|
|
||||||
|
|
||||||
this is actually a md file, try putting .md at the end of this path
|
|
||||||
@@ -86,19 +86,19 @@ protogen v1.0, toaster v1.0
|
|||||||
<li>
|
<li>
|
||||||
<a href="/events/crittersmk"><b>Critters MK</b></a> - A furmeet in Milton Keynes.
|
<a href="/events/crittersmk"><b>Critters MK</b></a> - A furmeet in Milton Keynes.
|
||||||
<div class="fur-meet-gallery-small">
|
<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/photos/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/photos/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/photos/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">
|
<img src="/static/content/photos/fur_meets/26-07-2025_critters_mk/PXL_20250726_155434701.jpg" alt="Critters MK">
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/events/paws-n-pistons"><b>Paws'N'Pistons</b></a> - A furry car meet around the UK.
|
<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">
|
<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/photos/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/photos/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/photos/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/photos/fur_meets/03-08-2025_paws_n_pistons/PXL_20250803_183614897.jpg" alt="Paws'N'Pistons">
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<p>Click on the links to view more photos from each event :3</p>
|
<p>Click on the links to view more photos from each event :3</p>
|
||||||