Compare commits
172 Commits
v1.1
...
e489764ff8
| Author | SHA1 | Date | |
|---|---|---|---|
|
e489764ff8
|
|||
|
51c4416d4d
|
|||
|
|
a78d999a61 | ||
|
|
74afdc1b5b | ||
|
|
607fdd4d46 | ||
|
|
ca01b96e80 | ||
|
467faff592
|
|||
|
3791d0be6e
|
|||
|
06526ca92d
|
|||
|
e1ff6e42a5
|
|||
|
a6670f6533
|
|||
|
598cea1ac8
|
|||
|
9b4febeddb
|
|||
|
c15a5d5a8b
|
|||
|
720e59c144
|
|||
|
c45a30675c
|
|||
|
9e8e23165e
|
|||
|
53e05922bf
|
|||
|
0be0dad1b2
|
|||
|
f404d55935
|
|||
|
009c2b430c
|
|||
|
ed96fbcc29
|
|||
|
4555ef5da2
|
|||
|
1335a73eb6
|
|||
|
b8f3039629
|
|||
|
1888160fa5
|
|||
|
7dd0f839cf
|
|||
|
5a0068586a
|
|||
|
8079780c08
|
|||
|
72b8dae35e
|
|||
|
323ace5775
|
|||
|
c2803e372a
|
|||
|
2a9e704f29
|
|||
|
0c490625a9
|
|||
|
b9753617ad
|
|||
|
b87d19c5d9
|
|||
|
67e8b4cf7e
|
|||
|
bfc6652f29
|
|||
|
38372c0cff
|
|||
|
dd64313006
|
|||
|
9e20a6171a
|
|||
|
da347fd860
|
|||
|
776b7de753
|
|||
|
7b2b3659bb
|
|||
|
872373dffd
|
|||
|
8d832372cd
|
|||
|
03dae87272
|
|||
|
4c654fcb78
|
|||
|
c9542e4af7
|
|||
|
e184375897
|
|||
|
844f1b52e2
|
|||
|
19c51c3665
|
|||
|
85ebd460ed
|
|||
|
50879b4f0e
|
|||
|
6c09923281
|
|||
|
332c408b89
|
|||
|
4ae6f7bb99
|
|||
|
d4d6b47225
|
|||
|
9809fe0695
|
|||
|
3522389422
|
|||
|
2979d3c4de
|
|||
|
a8b2c02164
|
|||
|
372ba908b8
|
|||
|
1145b9205c
|
|||
|
a71c5b6663
|
|||
|
724e800201
|
|||
|
abcaa9283d
|
|||
|
e175f68d25
|
|||
|
80b6a9bf46
|
|||
|
b089b8c0a8
|
|||
|
8f774ba8f0
|
|||
|
f4f5f47ee7
|
|||
|
16f17a9486
|
|||
|
72483674f6
|
|||
|
b69c7f381b
|
|||
|
d7d4dbed8b
|
|||
|
2437b19836
|
|||
|
abd23e0eb8
|
|||
|
57a4b977ec
|
|||
|
7f591e2724
|
|||
|
3d5c16f9cb
|
|||
|
fdb5f84c92
|
|||
|
eaf363ee27
|
|||
|
0ea9db3473
|
|||
|
8d6acca5e9
|
|||
|
bfc1f0839a
|
|||
|
258061c64d
|
|||
|
399ac5f0da
|
|||
|
74362de02a
|
|||
|
9f7b93b8a1
|
|||
|
665921d046
|
|||
|
84cf772273
|
|||
|
22cd49a012
|
|||
|
00d035a0e8
|
|||
|
fc56cafab8
|
|||
|
eee87e6ca7
|
|||
|
09852f19b6
|
|||
|
8b464cd89d
|
|||
|
98597768f3
|
|||
|
08f80ddb5c
|
|||
|
33fd8136a7
|
|||
|
b2943bfeac
|
|||
|
c3c7c86a66
|
|||
|
8563a6857f
|
|||
|
1650d25d0f
|
|||
|
f4ee2297a7
|
|||
|
35ced02977
|
|||
|
45709632d5
|
|||
|
e65fe8cd30
|
|||
|
21f725baf3
|
|||
|
c2a1995292
|
|||
|
af93330bf5
|
|||
|
e43ae63d8a
|
|||
|
add64ba889
|
|||
|
8d13297cb0
|
|||
|
eee95df47c
|
|||
|
1febeaf8a3
|
|||
|
088be26b48
|
|||
|
7f055f3607
|
|||
|
5a455fcdca
|
|||
|
846e28d92f
|
|||
|
ec3563093f
|
|||
|
0ec96b4461
|
|||
|
873504ecbf
|
|||
|
51f4a3462b
|
|||
|
0656d8a95d
|
|||
|
a79c795672
|
|||
|
159a40ecb5
|
|||
|
9d359640d3
|
|||
|
f0c60a4cea
|
|||
|
2c5ac7b475
|
|||
|
6a64de155d
|
|||
|
f5ba80fc16
|
|||
|
f8f48e9353
|
|||
|
f734e9ade4
|
|||
|
95e161aba9
|
|||
|
e4331e712a
|
|||
|
4804016fdf
|
|||
|
ccb6cdb454
|
|||
|
dade1f5d8d
|
|||
|
cecfdea07f
|
|||
|
7bea502bdf
|
|||
|
1fd539bcb9
|
|||
|
f940f4418d
|
|||
|
de476eb7ab
|
|||
|
2fbf21eaaf
|
|||
|
8ccbe2ebf1
|
|||
|
d4136a396a
|
|||
|
622a4038ac
|
|||
|
a65896b80d
|
|||
|
415703e21b
|
|||
|
89bf5eecd7
|
|||
|
b4059910ec
|
|||
|
7896f71534
|
|||
|
f9b30a3fe6
|
|||
|
31c80276de
|
|||
|
840ba4c10c
|
|||
|
53bf28b208
|
|||
|
d651e3a20c
|
|||
|
fb376a4906
|
|||
|
cd69d86246
|
|||
|
1ae1eeb159
|
|||
|
5171fffab5
|
|||
|
ca1e99013d
|
|||
|
7c71c994f2
|
|||
|
70655a4d39
|
|||
|
1551799b88
|
|||
|
4ab0db973f
|
|||
|
a3a2748d15
|
|||
|
ce8897d578
|
|||
|
637562f920
|
|||
|
42aff1f455
|
33
.dockerignore
Normal file
@@ -0,0 +1,33 @@
|
||||
# Bytecode and virtualenvs
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.venv/
|
||||
.vscode/
|
||||
.vs/
|
||||
.ruff_check/
|
||||
.env
|
||||
|
||||
# Pycache in subdirectories
|
||||
**/__pycache__/
|
||||
**/*.pyc
|
||||
**/*.pyo
|
||||
|
||||
# Git and CI
|
||||
.git/
|
||||
.gitea/
|
||||
testing/
|
||||
tests/
|
||||
|
||||
# Build and docs
|
||||
Dockerfile
|
||||
NathanWoodburn.bsdesign
|
||||
LICENSE.txt
|
||||
README.md
|
||||
|
||||
|
||||
# Development caches
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
|
||||
18
.gitea/workflows/check.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Check Code Quality
|
||||
run-name: Ruff CI
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
RuffCheck:
|
||||
runs-on: [ubuntu-latest, amd]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
run: |
|
||||
apt update
|
||||
apt install -y python3 python3-pip
|
||||
- name: Install Ruff
|
||||
run: pip install ruff
|
||||
- name: Run Ruff
|
||||
run: ruff check .
|
||||
4
.gitignore
vendored
@@ -3,4 +3,6 @@ __pycache__/
|
||||
|
||||
.env
|
||||
.vs/
|
||||
.venv/
|
||||
.venv/
|
||||
*.tmp
|
||||
testing/
|
||||
|
||||
16
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
# uv version.
|
||||
rev: 0.9.8
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- id: uv-export
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
8
.well-known/assetlinks.json
Normal file
@@ -0,0 +1,8 @@
|
||||
[{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "au.woodburn.nathan",
|
||||
"sha256_cert_fingerprints": ["D8:97:22:C4:C5:AA:AC:6D:7B:57:F0:19:FF:A1:E7:2A:92:71:EE:CF:1F:E1:AF:5A:87:22:0D:00:76:9D:83:80"]
|
||||
}
|
||||
}]
|
||||
@@ -1,6 +1,6 @@
|
||||
I hereby confirm that I am the owner of the Bitcoin address bc1q60qy4s6gd7se8z6nc4wu49g4u5y8k2epd3djzc.
|
||||
I hereby confirm that I am the owner of the Bitcoin address bc1qzsp3wc2nwayl8awun57tggjn92w0awp9mzsn90.
|
||||
Nathan.Woodburn/
|
||||
--------------------
|
||||
H3oLoxLyax3gW6KkGfJiarseaktWWydyfS2TZ1zGoDlkIoMG/nIlNWNC5xc/uHCdPs+CIJgxNRq0GQI91qkalpg=
|
||||
HzEsSV17nmbMwnz55sW6jW/AnmYxW0TAdgiJsYRUyWs7WjBaGlzrvXkH1R8qosQXSvQ1nZNe9dS5SUCZtbNZzuM=
|
||||
--------------------
|
||||
You can verify this signature by pasting it into a signature verification tool such as https://www.verifybitcoinmessage.com/.
|
||||
You can verify this signature by pasting it into a signature verification tool such as https://www.verifybitcoinmessage.com/. Don't forget to remove trailing newlines.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ETH":"woodburn.au",
|
||||
"HNS":"woodburn",
|
||||
"HNS":"nathan.woodburn",
|
||||
"SOL":"woodburn.sol",
|
||||
"ADA": "$nathanwoodburn",
|
||||
"MATIC": "woodburn.au",
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
"name": "USDC",
|
||||
"chain": "NOBLE"
|
||||
},
|
||||
{
|
||||
"symbol": "PYUSD",
|
||||
"name": "PayPal USD",
|
||||
"chain": "ETH"
|
||||
},
|
||||
{
|
||||
"symbol": "PYUSD",
|
||||
"name": "PayPal USD",
|
||||
"chain": "SOL"
|
||||
},
|
||||
{
|
||||
"symbol": "WDBRN",
|
||||
"name": "Woodburn",
|
||||
@@ -69,5 +79,10 @@
|
||||
"name": "Bitcoin Lightning",
|
||||
"chain": "null",
|
||||
"address": "thinbadger6@primal.net"
|
||||
},
|
||||
{
|
||||
"symbol": "stWDBRN",
|
||||
"name": "Woodburn Vault",
|
||||
"chain": "SOL"
|
||||
}
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
bc1q60qy4s6gd7se8z6nc4wu49g4u5y8k2epd3djzc
|
||||
bc1qzsp3wc2nwayl8awun57tggjn92w0awp9mzsn90
|
||||
@@ -1 +1 @@
|
||||
hs1qk4sq6mk3kcshp02xgchukv09m38czdnq5qv76w
|
||||
hs1qh7uzytf2ftwkd9dmjjs7az9qfver5m7dd7x4ej
|
||||
66
Dockerfile
@@ -1,17 +1,63 @@
|
||||
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
### Build stage ###
|
||||
FROM python:3.13-alpine AS build
|
||||
|
||||
# Install build dependencies for Pillow and other native wheels
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
jpeg-dev zlib-dev freetype-dev
|
||||
|
||||
# Copy uv (fast Python package manager)
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.8.21 /uv /uvx /bin/
|
||||
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml uv.lock ./
|
||||
|
||||
COPY requirements.txt /app
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
python3 -m pip install -r requirements.txt
|
||||
# Install dependencies into a virtual environment
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --locked
|
||||
|
||||
COPY . /app
|
||||
# Copy only app source files
|
||||
COPY blueprints blueprints
|
||||
COPY main.py server.py curl.py tools.py mail.py cache_helper.py ./
|
||||
COPY templates templates
|
||||
COPY data data
|
||||
COPY pwa pwa
|
||||
COPY .well-known .well-known
|
||||
|
||||
# Add mount point for data volume
|
||||
# VOLUME /data
|
||||
# Clean up caches and pycache
|
||||
RUN rm -rf /root/.cache/uv
|
||||
RUN find . -type d -name "__pycache__" -exec rm -rf {} +
|
||||
|
||||
ENTRYPOINT ["python3"]
|
||||
CMD ["main.py"]
|
||||
|
||||
FROM builder as dev-envs
|
||||
### Runtime stage ###
|
||||
FROM python:3.13-alpine AS runtime
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 appgroup && \
|
||||
adduser -D -u 1001 -G appgroup -h /app appuser
|
||||
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
|
||||
# Copy only what’s needed for runtime
|
||||
COPY --from=build --chown=appuser:appgroup /app/.venv /app/.venv
|
||||
COPY --from=build --chown=appuser:appgroup /app/blueprints /app/blueprints
|
||||
COPY --from=build --chown=appuser:appgroup /app/templates /app/templates
|
||||
COPY --from=build --chown=appuser:appgroup /app/data /app/data
|
||||
COPY --from=build --chown=appuser:appgroup /app/pwa /app/pwa
|
||||
COPY --from=build --chown=appuser:appgroup /app/.well-known /app/.well-known
|
||||
COPY --from=build --chown=appuser:appgroup /app/main.py /app/
|
||||
COPY --from=build --chown=appuser:appgroup /app/server.py /app/
|
||||
COPY --from=build --chown=appuser:appgroup /app/curl.py /app/
|
||||
COPY --from=build --chown=appuser:appgroup /app/tools.py /app/
|
||||
COPY --from=build --chown=appuser:appgroup /app/mail.py /app/
|
||||
COPY --from=build --chown=appuser:appgroup /app/cache_helper.py /app/
|
||||
|
||||
USER appuser
|
||||
EXPOSE 5000
|
||||
|
||||
ENTRYPOINT ["python3", "main.py"]
|
||||
BIN
NathanWoodburn.bsdesign
Normal file
43
addCoin.py
@@ -1,35 +1,38 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
if not os.path.exists('.well-known/wallets'):
|
||||
os.makedirs('.well-known/wallets')
|
||||
if not os.path.exists(".well-known/wallets"):
|
||||
os.makedirs(".well-known/wallets")
|
||||
|
||||
def addCoin(token:str, name:str, address:str):
|
||||
with open('.well-known/wallets/'+token.upper(),'w') as f:
|
||||
|
||||
def addCoin(token: str, name: str, address: str):
|
||||
with open(".well-known/wallets/" + token.upper(), "w") as f:
|
||||
f.write(address)
|
||||
|
||||
with open('.well-known/wallets/.coins','r') as f:
|
||||
|
||||
with open(".well-known/wallets/.coins", "r") as f:
|
||||
coins = json.load(f)
|
||||
|
||||
coins[token.upper()] = f'{name} ({token.upper()})'
|
||||
with open('.well-known/wallets/.coins','w') as f:
|
||||
|
||||
coins[token.upper()] = f"{name} ({token.upper()})"
|
||||
with open(".well-known/wallets/.coins", "w") as f:
|
||||
f.write(json.dumps(coins, indent=4))
|
||||
|
||||
def addDomain(token:str, domain:str):
|
||||
with open('.well-known/wallets/.domains','r') as f:
|
||||
|
||||
def addDomain(token: str, domain: str):
|
||||
with open(".well-known/wallets/.domains", "r") as f:
|
||||
domains = json.load(f)
|
||||
|
||||
|
||||
domains[token.upper()] = domain
|
||||
with open('.well-known/wallets/.domains','w') as f:
|
||||
with open(".well-known/wallets/.domains", "w") as f:
|
||||
f.write(json.dumps(domains, indent=4))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Ask user for token
|
||||
token = input('Enter token symbol: ')
|
||||
name = input('Enter token name: ')
|
||||
address = input('Enter wallet address: ')
|
||||
token = input("Enter token symbol: ")
|
||||
name = input("Enter token name: ")
|
||||
address = input("Enter wallet address: ")
|
||||
addCoin(token, name, address)
|
||||
|
||||
if input('Do you want to add a domain? (y/n): ').lower() == 'y':
|
||||
domain = input('Enter domain: ')
|
||||
addDomain(token, domain)
|
||||
if input("Do you want to add a domain? (y/n): ").lower() == "y":
|
||||
domain = input("Enter domain: ")
|
||||
addDomain(token, domain)
|
||||
|
||||
38
blueprints/acme.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from flask import Blueprint, request
|
||||
import os
|
||||
from cloudflare import Cloudflare
|
||||
from tools import json_response
|
||||
|
||||
app = Blueprint("acme", __name__)
|
||||
|
||||
|
||||
@app.route("/hnsdoh-acme", methods=["POST"])
|
||||
def post():
|
||||
# Get the TXT record from the request
|
||||
if not request.is_json or not request.json:
|
||||
return json_response(request, "415 Unsupported Media Type", 415)
|
||||
if "txt" not in request.json or "auth" not in request.json:
|
||||
return json_response(request, "400 Bad Request", 400)
|
||||
|
||||
txt = request.json["txt"]
|
||||
auth = request.json["auth"]
|
||||
if auth != os.getenv("CF_AUTH"):
|
||||
return json_response(request, "401 Unauthorized", 401)
|
||||
|
||||
cf = Cloudflare(api_token=os.getenv("CF_TOKEN"))
|
||||
zone = cf.zones.list(name="hnsdoh.com").to_dict()
|
||||
zone_id = zone["result"][0]["id"] # type: ignore
|
||||
existing_records = cf.dns.records.list(
|
||||
zone_id=zone_id,
|
||||
type="TXT",
|
||||
name="_acme-challenge.hnsdoh.com", # type: ignore
|
||||
).to_dict()
|
||||
record_id = existing_records["result"][0]["id"] # type: ignore
|
||||
cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id)
|
||||
cf.dns.records.create(
|
||||
zone_id=zone_id,
|
||||
type="TXT",
|
||||
name="_acme-challenge",
|
||||
content=txt,
|
||||
)
|
||||
return json_response(request, "Success", 200)
|
||||
330
blueprints/api.py
Normal file
@@ -0,0 +1,330 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import os
|
||||
import datetime
|
||||
import requests
|
||||
import re
|
||||
from mail import sendEmail
|
||||
from tools import getClientIP, getGitCommit, json_response, parse_date, get_tools_data
|
||||
from blueprints import sol
|
||||
from dateutil import parser as date_parser
|
||||
from blueprints.spotify import get_spotify_track
|
||||
from cache_helper import get_nc_config, get_git_latest_activity
|
||||
|
||||
# Constants
|
||||
HTTP_OK = 200
|
||||
HTTP_BAD_REQUEST = 400
|
||||
HTTP_UNAUTHORIZED = 401
|
||||
HTTP_NOT_FOUND = 404
|
||||
HTTP_UNSUPPORTED_MEDIA = 415
|
||||
HTTP_SERVER_ERROR = 500
|
||||
|
||||
app = Blueprint("api", __name__, url_prefix="/api/v1")
|
||||
# Register solana blueprint
|
||||
app.register_blueprint(sol.app)
|
||||
|
||||
|
||||
@app.route("/", strict_slashes=False)
|
||||
@app.route("/help")
|
||||
def help():
|
||||
"""Provide API documentation and help."""
|
||||
return jsonify(
|
||||
{
|
||||
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
|
||||
"endpoints": {
|
||||
"/time": "Get the current time",
|
||||
"/timezone": "Get the current timezone",
|
||||
"/message": "Get the message from the config",
|
||||
"/project": "Get the current project from git",
|
||||
"/version": "Get the current version of the website",
|
||||
"/page_date?url=URL&verbose=BOOL": "Get the last modified date of a webpage (verbose is optional, default false)",
|
||||
"/tools": "Get a list of tools used by Nathan Woodburn",
|
||||
"/playing": "Get the currently playing Spotify track",
|
||||
"/status": "Just check if the site is up",
|
||||
"/ping": "Just check if the site is up",
|
||||
"/ip": "Get your IP address",
|
||||
"/headers": "Get your request headers",
|
||||
"/help": "Get this help message",
|
||||
},
|
||||
"base_url": "/api/v1",
|
||||
"version": getGitCommit(),
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/status")
|
||||
@app.route("/ping")
|
||||
def status():
|
||||
return json_response(request, "200 OK", HTTP_OK)
|
||||
|
||||
|
||||
@app.route("/version")
|
||||
def version():
|
||||
"""Get the current version of the website."""
|
||||
return jsonify({"version": getGitCommit()})
|
||||
|
||||
|
||||
@app.route("/time")
|
||||
def time():
|
||||
"""Get the current time in the configured timezone."""
|
||||
nc_config = get_nc_config()
|
||||
timezone_offset = datetime.timedelta(hours=nc_config["time-zone"])
|
||||
timezone = datetime.timezone(offset=timezone_offset)
|
||||
current_time = datetime.datetime.now(tz=timezone)
|
||||
return jsonify(
|
||||
{
|
||||
"timestring": current_time.strftime("%A, %B %d, %Y %I:%M %p"),
|
||||
"timestamp": current_time.timestamp(),
|
||||
"timezone": nc_config["time-zone"],
|
||||
"timeISO": current_time.isoformat(),
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/timezone")
|
||||
def timezone():
|
||||
"""Get the current timezone setting."""
|
||||
nc_config = get_nc_config()
|
||||
return jsonify(
|
||||
{
|
||||
"timezone": nc_config["time-zone"],
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/message")
|
||||
def message():
|
||||
"""Get the message from the configuration."""
|
||||
nc_config = get_nc_config()
|
||||
return jsonify(
|
||||
{"message": nc_config["message"], "ip": getClientIP(request), "status": HTTP_OK}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/ip")
|
||||
def ip():
|
||||
"""Get the client's IP address."""
|
||||
return jsonify({"ip": getClientIP(request), "status": HTTP_OK})
|
||||
|
||||
|
||||
@app.route("/email", methods=["POST"])
|
||||
def email_post():
|
||||
"""Send an email via the API (requires API key)."""
|
||||
# Verify json
|
||||
if not request.is_json:
|
||||
return json_response(
|
||||
request, "415 Unsupported Media Type", HTTP_UNSUPPORTED_MEDIA
|
||||
)
|
||||
|
||||
# Check if api key sent
|
||||
data = request.json
|
||||
if not data:
|
||||
return json_response(request, "400 Bad Request", HTTP_BAD_REQUEST)
|
||||
|
||||
if "key" not in data:
|
||||
return json_response(request, "400 Bad Request 'key' missing", HTTP_BAD_REQUEST)
|
||||
|
||||
if data["key"] != os.getenv("EMAIL_KEY"):
|
||||
return json_response(request, "401 Unauthorized", HTTP_UNAUTHORIZED)
|
||||
|
||||
# TODO: Add client info to email
|
||||
return sendEmail(data)
|
||||
|
||||
|
||||
@app.route("/project")
|
||||
def project():
|
||||
"""Get information about the current git project."""
|
||||
git = get_git_latest_activity()
|
||||
repo_name = git["repo"]["name"].lower()
|
||||
repo_description = git["repo"]["description"]
|
||||
|
||||
gitinfo = {
|
||||
"name": repo_name,
|
||||
"description": repo_description,
|
||||
"url": git["repo"]["html_url"],
|
||||
"website": git["repo"].get("website"),
|
||||
}
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"repo_name": repo_name,
|
||||
"repo_description": repo_description,
|
||||
"repo": gitinfo,
|
||||
"ip": getClientIP(request),
|
||||
"status": HTTP_OK,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/tools")
|
||||
def tools():
|
||||
"""Get a list of tools used by Nathan Woodburn."""
|
||||
try:
|
||||
tools = get_tools_data()
|
||||
except Exception as e:
|
||||
print(f"Error getting tools data: {e}")
|
||||
return json_response(request, "500 Internal Server Error", HTTP_SERVER_ERROR)
|
||||
|
||||
return json_response(request, {"tools": tools}, HTTP_OK)
|
||||
|
||||
|
||||
@app.route("/playing")
|
||||
def playing():
|
||||
"""Get the currently playing Spotify track."""
|
||||
track_info = get_spotify_track()
|
||||
if "error" in track_info:
|
||||
return json_response(request, track_info, HTTP_OK)
|
||||
return json_response(request, {"spotify": track_info}, HTTP_OK)
|
||||
|
||||
|
||||
@app.route("/headers")
|
||||
def headers():
|
||||
"""Get the request headers."""
|
||||
headers = dict(request.headers)
|
||||
|
||||
# For each header, convert list-like headers to lists
|
||||
toremove = []
|
||||
for key, _ in headers.items():
|
||||
# If header is like X- something
|
||||
if key.startswith("X-"):
|
||||
# Remove from headers
|
||||
toremove.append(key)
|
||||
|
||||
for key in toremove:
|
||||
headers.pop(key)
|
||||
|
||||
return jsonify({"headers": headers, "ip": getClientIP(request), "status": HTTP_OK})
|
||||
|
||||
|
||||
@app.route("/page_date")
|
||||
def page_date():
|
||||
url = request.args.get("url")
|
||||
if not url:
|
||||
return json_response(request, "400 Bad Request 'url' missing", HTTP_BAD_REQUEST)
|
||||
|
||||
verbose = request.args.get("verbose", "").lower() in ["true", "1", "yes", "y"]
|
||||
|
||||
if not url.startswith(("https://", "http://")):
|
||||
return json_response(request, "400 Bad Request 'url' invalid", HTTP_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
r = requests.get(url, timeout=5)
|
||||
r.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return json_response(
|
||||
request, f"400 Bad Request 'url' unreachable: {e}", HTTP_BAD_REQUEST
|
||||
)
|
||||
|
||||
page_text = r.text
|
||||
|
||||
# Remove ordinal suffixes globally
|
||||
page_text = re.sub(r"(\d+)(st|nd|rd|th)", r"\1", page_text, flags=re.IGNORECASE)
|
||||
# Remove HTML comments
|
||||
page_text = re.sub(r"<!--.*?-->", "", page_text, flags=re.DOTALL)
|
||||
|
||||
date_patterns = [
|
||||
r"(\d{4})[/-](\d{1,2})[/-](\d{1,2})", # YYYY-MM-DD
|
||||
r"(\d{1,2})[/-](\d{1,2})[/-](\d{4})", # DD-MM-YYYY
|
||||
r"(?:Last updated:|Updated:|Updated last:)?\s*(\d{1,2})\s+([A-Za-z]{3,9})[, ]?\s*(\d{4})", # DD Month YYYY
|
||||
r"(?:\b\w+\b\s+){0,3}([A-Za-z]{3,9})\s+(\d{1,2}),?\s*(\d{4})", # Month DD, YYYY with optional words
|
||||
r"\b(\d{4})(\d{2})(\d{2})\b", # YYYYMMDD
|
||||
r"(?:Last updated:|Updated:|Last update)?\s*([A-Za-z]{3,9})\s+(\d{4})", # Month YYYY only
|
||||
]
|
||||
|
||||
# Structured data patterns
|
||||
json_date_patterns = {
|
||||
r'"datePublished"\s*:\s*"([^"]+)"': "published",
|
||||
r'"dateModified"\s*:\s*"([^"]+)"': "modified",
|
||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:published_time"\s+content\s*=\s*"([^"]+)"': "published",
|
||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:modified_time"\s+content\s*=\s*"([^"]+)"': "modified",
|
||||
r'<time\s+datetime\s*=\s*"([^"]+)"': "published",
|
||||
}
|
||||
|
||||
found_dates = []
|
||||
|
||||
# Extract content dates
|
||||
for idx, pattern in enumerate(date_patterns):
|
||||
for match in re.findall(pattern, page_text):
|
||||
if not match:
|
||||
continue
|
||||
groups = match[-3:] # last three elements
|
||||
found_dates.append([groups, idx, "content"])
|
||||
|
||||
# Extract structured data dates
|
||||
for pattern, date_type in json_date_patterns.items():
|
||||
for match in re.findall(pattern, page_text):
|
||||
try:
|
||||
dt = date_parser.isoparse(match)
|
||||
formatted_date = dt.strftime("%Y-%m-%d")
|
||||
found_dates.append([[formatted_date], -1, date_type])
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
if not found_dates:
|
||||
return json_response(request, "Date not found on page", HTTP_BAD_REQUEST)
|
||||
|
||||
today = datetime.date.today()
|
||||
tolerance_date = today + datetime.timedelta(
|
||||
days=1
|
||||
) # Allow for slight future dates (e.g., time zones)
|
||||
# When processing dates
|
||||
processed_dates = []
|
||||
for date_groups, pattern_format, date_type in found_dates:
|
||||
if pattern_format == -1:
|
||||
# Already formatted date
|
||||
try:
|
||||
dt = datetime.datetime.strptime(date_groups[0], "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
parsed_date = parse_date(date_groups)
|
||||
if not parsed_date:
|
||||
continue
|
||||
dt = datetime.datetime.strptime(parsed_date, "%Y-%m-%d").date()
|
||||
|
||||
# Only keep dates in the past (with tolerance)
|
||||
if dt <= tolerance_date:
|
||||
date_obj = {"date": dt.strftime("%Y-%m-%d"), "type": date_type}
|
||||
if verbose:
|
||||
if pattern_format == -1:
|
||||
date_obj.update(
|
||||
{
|
||||
"source": "metadata",
|
||||
"pattern_used": pattern_format,
|
||||
"raw": date_groups[0],
|
||||
}
|
||||
)
|
||||
else:
|
||||
date_obj.update(
|
||||
{
|
||||
"source": "content",
|
||||
"pattern_used": pattern_format,
|
||||
"raw": " ".join(date_groups),
|
||||
}
|
||||
)
|
||||
processed_dates.append(date_obj)
|
||||
|
||||
if not processed_dates:
|
||||
if verbose:
|
||||
return jsonify(
|
||||
{
|
||||
"message": "No valid dates found on page",
|
||||
"found_dates": found_dates,
|
||||
"processed_dates": processed_dates,
|
||||
}
|
||||
), HTTP_BAD_REQUEST
|
||||
return json_response(request, "No valid dates found on page", HTTP_BAD_REQUEST)
|
||||
# Sort dates and return latest
|
||||
processed_dates.sort(key=lambda x: x["date"])
|
||||
latest = processed_dates[-1]
|
||||
|
||||
response = {"latest": latest["date"], "type": latest["type"]}
|
||||
if verbose:
|
||||
response["dates"] = processed_dates
|
||||
|
||||
return json_response(request, response, HTTP_OK)
|
||||
189
blueprints/blog.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import os
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from tools import isCLI, getClientIP, getHandshakeScript
|
||||
|
||||
app = Blueprint("blog", __name__, url_prefix="/blog")
|
||||
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def list_page_files():
|
||||
blog_pages = os.listdir("data/blog")
|
||||
# Sort pages by modified time, newest first
|
||||
blog_pages.sort(
|
||||
key=lambda x: os.path.getmtime(os.path.join("data/blog", x)), reverse=True
|
||||
)
|
||||
|
||||
# Remove .md extension
|
||||
blog_pages = [
|
||||
page.removesuffix(".md") for page in blog_pages if page.endswith(".md")
|
||||
]
|
||||
|
||||
return blog_pages
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def get_blog_content(date):
|
||||
"""Get and cache blog content."""
|
||||
if not os.path.exists(f"data/blog/{date}.md"):
|
||||
return None
|
||||
|
||||
with open(f"data/blog/{date}.md", "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def render_markdown_to_html(content):
|
||||
"""Convert markdown to HTML with caching."""
|
||||
html = markdown.markdown(
|
||||
content, extensions=["sane_lists", "codehilite", "fenced_code"]
|
||||
)
|
||||
# Add target="_blank" to all links
|
||||
html = html.replace('<a href="', '<a target="_blank" href="')
|
||||
html = html.replace("<h4", "<h4 style='margin-bottom:0px;'")
|
||||
html = fix_numbered_lists(html)
|
||||
return html
|
||||
|
||||
|
||||
def render_page(date, handshake_scripts=None):
|
||||
# Get cached content
|
||||
content = get_blog_content(date)
|
||||
if content is None:
|
||||
return render_template("404.html"), 404
|
||||
|
||||
# Get the title from the file name
|
||||
title = date.removesuffix(".md").replace("_", " ")
|
||||
# Convert the md to html (cached)
|
||||
html_content = render_markdown_to_html(content)
|
||||
|
||||
return render_template(
|
||||
"blog/template.html",
|
||||
title=title,
|
||||
content=html_content,
|
||||
handshake_scripts=handshake_scripts,
|
||||
)
|
||||
|
||||
|
||||
def fix_numbered_lists(html):
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
|
||||
# Find the <p> tag containing numbered steps
|
||||
paragraphs = soup.find_all("p")
|
||||
for p in paragraphs:
|
||||
content = p.decode_contents() # type: ignore
|
||||
|
||||
# Check for likely numbered step structure
|
||||
if re.search(r"1\.\s", content):
|
||||
# Split into pre-list and numbered steps
|
||||
# Match: <br>, optional whitespace, then a number and dot
|
||||
parts = re.split(r"(?:<br\s*/?>)?\s*(\d+)\.\s", content)
|
||||
|
||||
# Result: [pre-text, '1', step1, '2', step2, ..., '10', step10]
|
||||
pre_text = parts[0].strip()
|
||||
steps = parts[1:]
|
||||
|
||||
# Assemble the ordered list
|
||||
ol_items = []
|
||||
for i in range(0, len(steps), 2):
|
||||
if i + 1 < len(steps):
|
||||
step_html = steps[i + 1].strip()
|
||||
ol_items.append(f"<li style='list-style: auto;'>{step_html}</li>")
|
||||
|
||||
# Build the final list HTML
|
||||
ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>"
|
||||
|
||||
# Rebuild paragraph with optional pre-text
|
||||
new_html = f"{pre_text}<br />\n{ol_html}" if pre_text else ol_html
|
||||
|
||||
# Replace old <p> with parsed version
|
||||
new_fragment = BeautifulSoup(new_html, "html.parser")
|
||||
p.replace_with(new_fragment)
|
||||
break # Only process the first matching <p>
|
||||
|
||||
return str(soup)
|
||||
|
||||
|
||||
def render_home(handshake_scripts: str | None = None):
|
||||
# Get a list of pages
|
||||
blog_pages = list_page_files()
|
||||
# Create a html list of pages
|
||||
blog_pages = [
|
||||
f"""<li class="list-group-item">
|
||||
|
||||
<p style="margin-bottom: 0px;"><a href='/blog/{page}'>{page.replace("_", " ")}</a></p>
|
||||
</li>"""
|
||||
for page in blog_pages
|
||||
]
|
||||
# Join the list
|
||||
blog_pages = "\n".join(blog_pages)
|
||||
# Render the template
|
||||
return render_template(
|
||||
"blog/blog.html",
|
||||
blogs=blog_pages,
|
||||
handshake_scripts=handshake_scripts,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", strict_slashes=False)
|
||||
def index():
|
||||
if not isCLI(request):
|
||||
return render_home(handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
# Get a list of pages
|
||||
blog_pages = list_page_files()
|
||||
# Create a html list of pages
|
||||
blog_pages = [
|
||||
{
|
||||
"name": page.replace("_", " "),
|
||||
"url": f"/blog/{page}",
|
||||
"download": f"/blog/{page}.md",
|
||||
}
|
||||
for page in blog_pages
|
||||
]
|
||||
|
||||
# Render the template
|
||||
return jsonify(
|
||||
{
|
||||
"status": 200,
|
||||
"message": "Check out my various blog postsa",
|
||||
"ip": getClientIP(request),
|
||||
"blogs": blog_pages,
|
||||
}
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def path(path):
|
||||
if not isCLI(request):
|
||||
return render_page(path, handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
# Get cached content
|
||||
content = get_blog_content(path)
|
||||
if content is None:
|
||||
return render_template("404.html"), 404
|
||||
|
||||
# Get the title from the file name
|
||||
title = path.replace("_", " ")
|
||||
return jsonify(
|
||||
{
|
||||
"status": 200,
|
||||
"message": f"Blog post: {title}",
|
||||
"ip": getClientIP(request),
|
||||
"title": title,
|
||||
"content": content,
|
||||
"download": f"/blog/{path}.md",
|
||||
}
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/<path:path>.md")
|
||||
def path_md(path):
|
||||
content = get_blog_content(path)
|
||||
if content is None:
|
||||
return render_template("404.html"), 404
|
||||
|
||||
# Return the raw markdown file
|
||||
return content, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
||||
223
blueprints/now.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from flask import Blueprint, render_template, make_response, request, jsonify
|
||||
import datetime
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from tools import getHandshakeScript, error_response, isCLI
|
||||
from curl import get_header, MAX_WIDTH
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
|
||||
# Create blueprint
|
||||
app = Blueprint("now", __name__, url_prefix="/now")
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def list_page_files():
|
||||
now_pages = os.listdir("templates/now")
|
||||
now_pages = [
|
||||
page for page in now_pages if page != "template.html" and page != "old.html"
|
||||
]
|
||||
now_pages.sort(reverse=True)
|
||||
return now_pages
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def list_dates():
|
||||
now_pages = list_page_files()
|
||||
now_dates = [page.split(".")[0] for page in now_pages]
|
||||
return now_dates
|
||||
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def get_latest_date(formatted=False):
|
||||
if formatted:
|
||||
date = list_dates()[0]
|
||||
date = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date = date.strftime("%A, %B %d, %Y")
|
||||
return date
|
||||
return list_dates()[0]
|
||||
|
||||
|
||||
def render_latest(handshake_scripts=None):
|
||||
now_page = list_dates()[0]
|
||||
return render(now_page, handshake_scripts=handshake_scripts)
|
||||
|
||||
|
||||
def render(date, handshake_scripts=None):
|
||||
# If the date is not available, render the latest page
|
||||
if date is None:
|
||||
return render_latest(handshake_scripts=handshake_scripts)
|
||||
# Remove .html
|
||||
date = date.removesuffix(".html")
|
||||
|
||||
if date not in list_dates():
|
||||
return error_response(request)
|
||||
|
||||
date_formatted = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
|
||||
return render_template(
|
||||
f"now/{date}.html", DATE=date_formatted, handshake_scripts=handshake_scripts
|
||||
)
|
||||
|
||||
|
||||
def render_curl(date=None):
|
||||
# If the date is not available, render the latest page
|
||||
if date is None:
|
||||
date = get_latest_date()
|
||||
|
||||
# Remove .html if present
|
||||
date = date.removesuffix(".html")
|
||||
|
||||
if date not in list_dates():
|
||||
return error_response(request)
|
||||
|
||||
# Format the date nicely
|
||||
date_formatted = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
|
||||
|
||||
# Load HTML
|
||||
with open(f"templates/now/{date}.html", "r", encoding="utf-8") as f:
|
||||
raw_html = f.read().replace("{{ date }}", date_formatted)
|
||||
soup = BeautifulSoup(raw_html, "html.parser")
|
||||
|
||||
posts = []
|
||||
|
||||
# Find divs matching your pattern
|
||||
divs = soup.find_all("div", style=re.compile(r"max-width:\s*700px", re.IGNORECASE))
|
||||
if not divs:
|
||||
return error_response(request, message="No content found for CLI rendering.")
|
||||
|
||||
for div in divs:
|
||||
# header could be h1/h2/h3 inside the div
|
||||
header_tag = div.find(["h1", "h2", "h3"]) # type: ignore
|
||||
# content is usually one or more <p> tags inside the div
|
||||
p_tags = div.find_all("p") # type: ignore
|
||||
|
||||
if header_tag and p_tags:
|
||||
header_text = header_tag.get_text(strip=True) # type: ignore
|
||||
content_lines = []
|
||||
|
||||
for p in p_tags:
|
||||
# Extract text
|
||||
text = p.get_text(strip=False)
|
||||
|
||||
# Extract any <a> links in the paragraph
|
||||
links = [a.get("href") for a in p.find_all("a", href=True)] # type: ignore
|
||||
# Set max width for text wrapping
|
||||
|
||||
# Wrap text manually
|
||||
wrapped_lines = []
|
||||
for line in text.splitlines():
|
||||
while len(line) > MAX_WIDTH:
|
||||
# Find last space within max_width
|
||||
split_at = line.rfind(" ", 0, MAX_WIDTH)
|
||||
if split_at == -1:
|
||||
split_at = MAX_WIDTH
|
||||
wrapped_lines.append(line[:split_at].rstrip())
|
||||
line = line[split_at:].lstrip()
|
||||
wrapped_lines.append(line)
|
||||
text = "\n".join(wrapped_lines)
|
||||
|
||||
if links:
|
||||
text += "\nLinks: " + ", ".join(links) # type: ignore
|
||||
|
||||
content_lines.append(text)
|
||||
|
||||
content_text = "\n\n".join(content_lines)
|
||||
posts.append({"header": header_text, "content": content_text})
|
||||
|
||||
# Build final response
|
||||
response = ""
|
||||
for post in posts:
|
||||
response += f"[1m{post['header']}[0m\n\n{post['content']}\n\n"
|
||||
|
||||
return render_template(
|
||||
"now.ascii", date=date_formatted, content=response, header=get_header()
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", strict_slashes=False)
|
||||
def index():
|
||||
if isCLI(request):
|
||||
return render_curl()
|
||||
return render_latest(handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def path(path):
|
||||
if isCLI(request):
|
||||
return render_curl(path)
|
||||
|
||||
return render(path, handshake_scripts=getHandshakeScript(request.host))
|
||||
|
||||
|
||||
@app.route("/old", strict_slashes=False)
|
||||
def old():
|
||||
now_dates = list_dates()[1:]
|
||||
if isCLI(request):
|
||||
response = ""
|
||||
for date in now_dates:
|
||||
link = date
|
||||
date_fmt = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date_fmt = date_fmt.strftime("%A, %B %d, %Y")
|
||||
response += f"{date_fmt} - /now/{link}\n"
|
||||
return render_template(
|
||||
"now.ascii", date="Old Now Pages", content=response, header=get_header()
|
||||
)
|
||||
|
||||
html = '<ul class="list-group">'
|
||||
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_date(True)}</li></a>'
|
||||
|
||||
for date in now_dates:
|
||||
link = date
|
||||
date = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||
date = date.strftime("%A, %B %d, %Y")
|
||||
html += f'<a style="text-decoration:none;" href="/now/{link}"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{date}</li></a>'
|
||||
|
||||
html += "</ul>"
|
||||
return render_template(
|
||||
"now/old.html",
|
||||
handshake_scripts=getHandshakeScript(request.host),
|
||||
now_pages=html,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/now.rss")
|
||||
@app.route("/now.xml")
|
||||
@app.route("/rss.xml")
|
||||
def rss():
|
||||
host = "https://" + request.host
|
||||
if ":" in request.host:
|
||||
host = "http://" + request.host
|
||||
# Generate RSS feed
|
||||
now_pages = list_page_files()
|
||||
rss = f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nathan.Woodburn/</title><link>{host}</link><description>See what I\'ve been up to</description><language>en-us</language><lastBuildDate>{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}</lastBuildDate><atom:link href="{host}/now.rss" rel="self" type="application/rss+xml" />'
|
||||
for page in now_pages:
|
||||
link = page.strip(".html")
|
||||
date = datetime.datetime.strptime(link, "%y_%m_%d")
|
||||
date = date.strftime("%A, %B %d, %Y")
|
||||
rss += f"<item><title>What's Happening {date}</title><link>{host}/now/{link}</link><description>Latest updates for {date}</description><guid>{host}/now/{link}</guid></item>"
|
||||
rss += "</channel></rss>"
|
||||
return make_response(rss, 200, {"Content-Type": "application/rss+xml"})
|
||||
|
||||
|
||||
@app.route("/now.json")
|
||||
def json():
|
||||
now_pages = list_page_files()
|
||||
host = "https://" + request.host
|
||||
if ":" in request.host:
|
||||
host = "http://" + request.host
|
||||
now_pages = [
|
||||
{
|
||||
"url": host + "/now/" + page.strip(".html"),
|
||||
"date": datetime.datetime.strptime(
|
||||
page.strip(".html"), "%y_%m_%d"
|
||||
).strftime("%A, %B %d, %Y"),
|
||||
"title": "What's Happening "
|
||||
+ datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime(
|
||||
"%A, %B %d, %Y"
|
||||
),
|
||||
}
|
||||
for page in now_pages
|
||||
]
|
||||
return jsonify(now_pages)
|
||||
60
blueprints/podcast.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from flask import Blueprint, make_response, request
|
||||
from tools import error_response
|
||||
import requests
|
||||
|
||||
app = Blueprint("podcast", __name__)
|
||||
|
||||
|
||||
@app.route("/ID1")
|
||||
def index():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/ID1/")
|
||||
def contents():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1/")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/ID1/<path:path>")
|
||||
def path(path):
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1/" + path)
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/ID1.xml")
|
||||
def xml():
|
||||
# Proxy to ID1 url
|
||||
req = requests.get("https://podcasts.c.woodburn.au/ID1.xml")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/podsync.opml")
|
||||
def podsync():
|
||||
req = requests.get("https://podcasts.c.woodburn.au/podsync.opml")
|
||||
if req.status_code != 200:
|
||||
return error_response(request, "Error from Podcast Server", req.status_code)
|
||||
return make_response(
|
||||
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
||||
)
|
||||
136
blueprints/sol.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from flask import Blueprint, request, jsonify, make_response
|
||||
from solders.pubkey import Pubkey
|
||||
from solana.rpc.api import Client
|
||||
from solders.system_program import TransferParams, transfer
|
||||
from solders.message import MessageV0
|
||||
from solders.transaction import VersionedTransaction
|
||||
from solders.null_signer import NullSigner
|
||||
import binascii
|
||||
import base64
|
||||
import os
|
||||
|
||||
app = Blueprint("sol", __name__)
|
||||
|
||||
SOLANA_HEADERS = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Action-Version": "2.4.2",
|
||||
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
||||
}
|
||||
|
||||
SOLANA_ADDRESS = None
|
||||
if os.path.isfile(".well-known/wallets/SOL"):
|
||||
with open(".well-known/wallets/SOL") as file:
|
||||
address = file.read()
|
||||
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
|
||||
|
||||
|
||||
def create_transaction(sender_address: str, amount: float) -> str:
|
||||
if SOLANA_ADDRESS is None:
|
||||
raise ValueError(
|
||||
"SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address."
|
||||
)
|
||||
# Create transaction
|
||||
sender = Pubkey.from_string(sender_address)
|
||||
transfer_ix = transfer(
|
||||
TransferParams(
|
||||
from_pubkey=sender,
|
||||
to_pubkey=SOLANA_ADDRESS,
|
||||
lamports=int(amount * 1000000000),
|
||||
)
|
||||
)
|
||||
solana_client = Client("https://api.mainnet-beta.solana.com")
|
||||
blockhashData = solana_client.get_latest_blockhash()
|
||||
blockhash = blockhashData.value.blockhash
|
||||
|
||||
msg = MessageV0.try_compile(
|
||||
payer=sender,
|
||||
instructions=[transfer_ix],
|
||||
address_lookup_table_accounts=[],
|
||||
recent_blockhash=blockhash,
|
||||
)
|
||||
tx = VersionedTransaction(message=msg, keypairs=[NullSigner(sender)])
|
||||
tx = bytes(tx).hex()
|
||||
raw_bytes = binascii.unhexlify(tx)
|
||||
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
|
||||
return base64_string
|
||||
|
||||
|
||||
def get_solana_address() -> str:
|
||||
if SOLANA_ADDRESS is None:
|
||||
raise ValueError(
|
||||
"SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address."
|
||||
)
|
||||
return str(SOLANA_ADDRESS)
|
||||
|
||||
|
||||
@app.route("/donate", methods=["GET", "OPTIONS"])
|
||||
def sol_donate():
|
||||
data = {
|
||||
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
|
||||
"label": "Donate to Nathan.Woodburn/",
|
||||
"title": "Donate to Nathan.Woodburn/",
|
||||
"description": "Student, developer, and crypto enthusiast",
|
||||
"links": {
|
||||
"actions": [
|
||||
{"label": "0.01 SOL", "href": "/api/v1/donate/0.01"},
|
||||
{"label": "0.1 SOL", "href": "/api/v1/donate/0.1"},
|
||||
{"label": "1 SOL", "href": "/api/v1/donate/1"},
|
||||
{
|
||||
"href": "/api/v1/donate/{amount}",
|
||||
"label": "Donate",
|
||||
"parameters": [
|
||||
{"name": "amount", "label": "Enter a custom SOL amount"}
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
response = make_response(jsonify(data), 200, SOLANA_HEADERS)
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = (
|
||||
"Content-Type,Authorization,Content-Encoding,Accept-Encoding,X-Action-Version,X-Blockchain-Ids"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/donate/<amount>")
|
||||
def sol_donate_amount(amount):
|
||||
data = {
|
||||
"icon": "https://nathan.woodburn.au/assets/img/profile.png",
|
||||
"label": f"Donate {amount} SOL to Nathan.Woodburn/",
|
||||
"title": "Donate to Nathan.Woodburn/",
|
||||
"description": f"Donate {amount} SOL to Nathan.Woodburn/",
|
||||
}
|
||||
return jsonify(data), 200, SOLANA_HEADERS
|
||||
|
||||
|
||||
@app.route("/donate/<amount>", methods=["POST"])
|
||||
def sol_donate_post(amount):
|
||||
if not request.json:
|
||||
return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
|
||||
|
||||
if "account" not in request.json:
|
||||
return jsonify({"message": "Error: No account provided"}), 400, SOLANA_HEADERS
|
||||
|
||||
sender = request.json["account"]
|
||||
|
||||
# Make sure amount is a number
|
||||
try:
|
||||
amount = float(amount)
|
||||
except ValueError:
|
||||
amount = 1 # Default to 1 SOL if invalid
|
||||
|
||||
if amount < 0.0001:
|
||||
return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
|
||||
|
||||
transaction = create_transaction(sender, amount)
|
||||
return (
|
||||
jsonify({"message": "Success", "transaction": transaction}),
|
||||
200,
|
||||
SOLANA_HEADERS,
|
||||
)
|
||||
137
blueprints/spotify.py
Normal file
@@ -0,0 +1,137 @@
|
||||
from flask import redirect, request, Blueprint, url_for
|
||||
from tools import json_response
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
import base64
|
||||
|
||||
app = Blueprint("spotify", __name__, url_prefix="/spotify")
|
||||
|
||||
CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
||||
CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
||||
ALLOWED_SPOTIFY_USER_ID = os.getenv("SPOTIFY_USER_ID")
|
||||
|
||||
SPOTIFY_AUTH_URL = "https://accounts.spotify.com/authorize"
|
||||
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
|
||||
SPOTIFY_CURRENTLY_PLAYING_URL = "https://api.spotify.com/v1/me/player/currently-playing"
|
||||
|
||||
SCOPE = "user-read-currently-playing user-read-playback-state"
|
||||
|
||||
ACCESS_TOKEN = None
|
||||
REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
|
||||
TOKEN_EXPIRES = 0
|
||||
|
||||
|
||||
def refresh_access_token():
|
||||
"""Refresh Spotify access token when expired."""
|
||||
global ACCESS_TOKEN, TOKEN_EXPIRES
|
||||
|
||||
# If no refresh token, cannot proceed
|
||||
if not REFRESH_TOKEN:
|
||||
return None
|
||||
|
||||
# If still valid, reuse it
|
||||
if ACCESS_TOKEN and time.time() < TOKEN_EXPIRES - 60:
|
||||
return ACCESS_TOKEN
|
||||
|
||||
auth_str = f"{CLIENT_ID}:{CLIENT_SECRET}"
|
||||
b64_auth = base64.b64encode(auth_str.encode()).decode()
|
||||
|
||||
data = {
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": REFRESH_TOKEN,
|
||||
}
|
||||
headers = {"Authorization": f"Basic {b64_auth}"}
|
||||
|
||||
response = requests.post(SPOTIFY_TOKEN_URL, data=data, headers=headers)
|
||||
if response.status_code != 200:
|
||||
print("Failed to refresh token:", response.text)
|
||||
return None
|
||||
|
||||
token_info = response.json()
|
||||
ACCESS_TOKEN = token_info["access_token"]
|
||||
TOKEN_EXPIRES = time.time() + token_info.get("expires_in", 3600)
|
||||
return ACCESS_TOKEN
|
||||
|
||||
|
||||
@app.route("/login")
|
||||
def login():
|
||||
auth_query = (
|
||||
f"{SPOTIFY_AUTH_URL}?response_type=code&client_id={CLIENT_ID}"
|
||||
f"&redirect_uri={url_for('spotify.callback', _external=True)}&scope={SCOPE}"
|
||||
)
|
||||
return redirect(auth_query)
|
||||
|
||||
|
||||
@app.route("/callback")
|
||||
def callback():
|
||||
code = request.args.get("code")
|
||||
if not code:
|
||||
return "Authorization failed.", 400
|
||||
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": url_for("spotify.callback", _external=True),
|
||||
"client_id": CLIENT_ID,
|
||||
"client_secret": CLIENT_SECRET,
|
||||
}
|
||||
response = requests.post(SPOTIFY_TOKEN_URL, data=data)
|
||||
token_info = response.json()
|
||||
if "access_token" not in token_info:
|
||||
return json_response(
|
||||
request, {"error": "Failed to obtain token", "details": token_info}, 400
|
||||
)
|
||||
|
||||
access_token = token_info["access_token"]
|
||||
me = requests.get(
|
||||
"https://api.spotify.com/v1/me",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
).json()
|
||||
|
||||
if me.get("id") != ALLOWED_SPOTIFY_USER_ID:
|
||||
return json_response(request, {"error": "Unauthorized user"}, 403)
|
||||
|
||||
global REFRESH_TOKEN
|
||||
REFRESH_TOKEN = token_info.get("refresh_token")
|
||||
print("Spotify authorization successful.")
|
||||
print("Refresh Token:", REFRESH_TOKEN)
|
||||
return redirect(url_for("spotify.currently_playing"))
|
||||
|
||||
|
||||
@app.route("/", strict_slashes=False)
|
||||
@app.route("/playing")
|
||||
def currently_playing():
|
||||
"""Public endpoint showing your current track."""
|
||||
track = get_spotify_track()
|
||||
return json_response(request, {"spotify": track}, 200)
|
||||
|
||||
|
||||
def get_spotify_track():
|
||||
"""Internal function to get current playing track without HTTP context."""
|
||||
token = refresh_access_token()
|
||||
if not token:
|
||||
return {"error": "Failed to refresh access token"}
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(SPOTIFY_CURRENTLY_PLAYING_URL, headers=headers)
|
||||
|
||||
if response.status_code == 204:
|
||||
return {"error": "Nothing is currently playing."}
|
||||
elif response.status_code != 200:
|
||||
return {"error": "Spotify API error", "status": response.status_code}
|
||||
|
||||
data = response.json()
|
||||
if not data.get("item"):
|
||||
return {"error": "Nothing is currently playing."}
|
||||
|
||||
track = {
|
||||
"song_name": data["item"]["name"],
|
||||
"artist": ", ".join([artist["name"] for artist in data["item"]["artists"]]),
|
||||
"album_name": data["item"]["album"]["name"],
|
||||
"album_art": data["item"]["album"]["images"][0]["url"],
|
||||
"is_playing": data["is_playing"],
|
||||
"progress_ms": data.get("progress_ms", 0),
|
||||
"duration_ms": data["item"].get("duration_ms", 1),
|
||||
}
|
||||
return track
|
||||
9
blueprints/template.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from flask import Blueprint, request
|
||||
from tools import json_response
|
||||
|
||||
app = Blueprint("template", __name__)
|
||||
|
||||
|
||||
@app.route("/", strict_slashes=False)
|
||||
def index():
|
||||
return json_response(request, "Success", 200)
|
||||
70
blueprints/wellknown.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from flask import (
|
||||
Blueprint,
|
||||
make_response,
|
||||
request,
|
||||
jsonify,
|
||||
send_from_directory,
|
||||
redirect,
|
||||
)
|
||||
from tools import error_response
|
||||
import os
|
||||
|
||||
app = Blueprint("well-known", __name__, url_prefix="/.well-known")
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def index(path):
|
||||
return send_from_directory(".well-known", path)
|
||||
|
||||
|
||||
@app.route("/wallets/<path:path>")
|
||||
def wallets(path):
|
||||
if path[0] == "." and "proof" not in path:
|
||||
return send_from_directory(
|
||||
".well-known/wallets", path, mimetype="application/json"
|
||||
)
|
||||
elif os.path.isfile(".well-known/wallets/" + path):
|
||||
address = ""
|
||||
with open(".well-known/wallets/" + path) as file:
|
||||
address = file.read()
|
||||
address = address.strip()
|
||||
return make_response(address, 200, {"Content-Type": "text/plain"})
|
||||
|
||||
if os.path.isfile(".well-known/wallets/" + path.upper()):
|
||||
return redirect("/.well-known/wallets/" + path.upper(), code=302)
|
||||
|
||||
return error_response(request)
|
||||
|
||||
|
||||
@app.route("/nostr.json")
|
||||
def nostr():
|
||||
# Get name parameter
|
||||
name = request.args.get("name")
|
||||
if name:
|
||||
return jsonify(
|
||||
{
|
||||
"names": {
|
||||
name: "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82"
|
||||
}
|
||||
}
|
||||
)
|
||||
return jsonify(
|
||||
{
|
||||
"names": {
|
||||
"nathan": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
|
||||
"_": "b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/xrp-ledger.toml")
|
||||
def xrp():
|
||||
# Create a response with the xrp-ledger.toml file
|
||||
with open(".well-known/xrp-ledger.toml") as file:
|
||||
toml = file.read()
|
||||
|
||||
response = make_response(toml, 200, {"Content-Type": "application/toml"})
|
||||
# Set cors headers
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
return response
|
||||
264
cache_helper.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Cache helper module for expensive API calls and configuration.
|
||||
Provides centralized caching with TTL for external API calls.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
# Cache storage for NC_CONFIG with timestamp
|
||||
_nc_config_cache = {"data": None, "timestamp": 0}
|
||||
_nc_config_ttl = 3600 # 1 hour cache
|
||||
|
||||
|
||||
def get_nc_config():
|
||||
"""
|
||||
Get NC_CONFIG with caching (1 hour TTL).
|
||||
Falls back to default config on error.
|
||||
|
||||
Returns:
|
||||
dict: Configuration dictionary
|
||||
"""
|
||||
global _nc_config_cache
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# Check if cache is valid
|
||||
if (
|
||||
_nc_config_cache["data"]
|
||||
and (current_time - _nc_config_cache["timestamp"]) < _nc_config_ttl
|
||||
):
|
||||
return _nc_config_cache["data"]
|
||||
|
||||
# Fetch new config
|
||||
try:
|
||||
config = requests.get(
|
||||
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json",
|
||||
timeout=5,
|
||||
).json()
|
||||
_nc_config_cache = {"data": config, "timestamp": current_time}
|
||||
return config
|
||||
except Exception as e:
|
||||
print(f"Error fetching NC_CONFIG: {e}")
|
||||
# Return cached data if available, otherwise default
|
||||
if _nc_config_cache["data"]:
|
||||
return _nc_config_cache["data"]
|
||||
return {"time-zone": 10, "message": ""}
|
||||
|
||||
|
||||
# Cache storage for git data
|
||||
_git_data_cache = {"data": None, "timestamp": 0}
|
||||
_git_data_ttl = 300 # 5 minutes cache
|
||||
|
||||
|
||||
def get_git_latest_activity():
|
||||
"""
|
||||
Get latest git activity with caching (5 minute TTL).
|
||||
|
||||
Returns:
|
||||
dict: Git activity data or default values
|
||||
"""
|
||||
global _git_data_cache
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# Check if cache is valid
|
||||
if (
|
||||
_git_data_cache["data"]
|
||||
and (current_time - _git_data_cache["timestamp"]) < _git_data_ttl
|
||||
):
|
||||
return _git_data_cache["data"]
|
||||
|
||||
# Fetch new data
|
||||
try:
|
||||
git = requests.get(
|
||||
"https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
|
||||
headers={
|
||||
"Authorization": os.getenv("GIT_AUTH") or os.getenv("git_token") or ""
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
git_data = git.json()
|
||||
if git_data and len(git_data) > 0:
|
||||
result = git_data[0]
|
||||
_git_data_cache = {"data": result, "timestamp": current_time}
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"Error fetching git data: {e}")
|
||||
|
||||
# Return cached or default
|
||||
if _git_data_cache["data"]:
|
||||
return _git_data_cache["data"]
|
||||
|
||||
return {
|
||||
"repo": {
|
||||
"html_url": "https://nathan.woodburn.au",
|
||||
"name": "nathanwoodburn.github.io",
|
||||
"description": "Personal website",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Cache storage for projects
|
||||
_projects_cache = {"data": None, "timestamp": 0}
|
||||
_projects_ttl = 7200 # 2 hours cache
|
||||
|
||||
|
||||
def get_projects(limit=3):
|
||||
"""
|
||||
Get projects list with caching (2 hour TTL).
|
||||
|
||||
Args:
|
||||
limit (int): Number of projects to return
|
||||
|
||||
Returns:
|
||||
list: List of project dictionaries
|
||||
"""
|
||||
global _projects_cache
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# Check if cache is valid
|
||||
if (
|
||||
_projects_cache["data"]
|
||||
and (current_time - _projects_cache["timestamp"]) < _projects_ttl
|
||||
):
|
||||
return _projects_cache["data"][:limit]
|
||||
|
||||
# Fetch new data
|
||||
try:
|
||||
projects = []
|
||||
projectsreq = requests.get(
|
||||
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos", timeout=5
|
||||
)
|
||||
projects = projectsreq.json()
|
||||
|
||||
# Check for pagination
|
||||
pageNum = 2
|
||||
while 'rel="next"' in projectsreq.headers.get("link", ""):
|
||||
projectsreq = requests.get(
|
||||
f"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos?page={pageNum}",
|
||||
timeout=5,
|
||||
)
|
||||
projects += projectsreq.json()
|
||||
pageNum += 1
|
||||
# Safety limit
|
||||
if pageNum > 10:
|
||||
break
|
||||
|
||||
# Process projects
|
||||
for project in projects:
|
||||
if project.get("avatar_url") in ("https://git.woodburn.au/", ""):
|
||||
project["avatar_url"] = "/favicon.png"
|
||||
project["name"] = project["name"].replace("_", " ").replace("-", " ")
|
||||
|
||||
# Sort by last updated
|
||||
projects_sorted = sorted(
|
||||
projects, key=lambda x: x.get("updated_at", ""), reverse=True
|
||||
)
|
||||
|
||||
# Remove duplicates by name
|
||||
seen_names = set()
|
||||
unique_projects = []
|
||||
for project in projects_sorted:
|
||||
if project["name"] not in seen_names:
|
||||
unique_projects.append(project)
|
||||
seen_names.add(project["name"])
|
||||
|
||||
_projects_cache = {"data": unique_projects, "timestamp": current_time}
|
||||
return unique_projects[:limit]
|
||||
except Exception as e:
|
||||
print(f"Error fetching projects: {e}")
|
||||
if _projects_cache["data"]:
|
||||
return _projects_cache["data"][:limit]
|
||||
return []
|
||||
|
||||
|
||||
# Cache storage for uptime status
|
||||
_uptime_cache = {"data": None, "timestamp": 0}
|
||||
_uptime_ttl = 300 # 5 minutes cache
|
||||
|
||||
|
||||
def get_uptime_status():
|
||||
"""
|
||||
Get uptime status with caching (5 minute TTL).
|
||||
|
||||
Returns:
|
||||
bool: True if services are up, False otherwise
|
||||
"""
|
||||
global _uptime_cache
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# Check if cache is valid
|
||||
if (
|
||||
_uptime_cache["data"] is not None
|
||||
and (current_time - _uptime_cache["timestamp"]) < _uptime_ttl
|
||||
):
|
||||
return _uptime_cache["data"]
|
||||
|
||||
# Fetch new data
|
||||
try:
|
||||
uptime = requests.get(
|
||||
"https://uptime.woodburn.au/api/status-page/main/badge", timeout=5
|
||||
)
|
||||
content = uptime.content.decode("utf-8").lower()
|
||||
status = "maintenance" in content or uptime.content.count(b"Up") > 1
|
||||
_uptime_cache = {"data": status, "timestamp": current_time}
|
||||
return status
|
||||
except Exception as e:
|
||||
print(f"Error fetching uptime: {e}")
|
||||
# Return cached or default (assume up)
|
||||
if _uptime_cache["data"] is not None:
|
||||
return _uptime_cache["data"]
|
||||
return True
|
||||
|
||||
|
||||
# Cached wallet data loaders
|
||||
@lru_cache(maxsize=1)
|
||||
def get_wallet_tokens():
|
||||
"""
|
||||
Get wallet tokens with caching.
|
||||
|
||||
Returns:
|
||||
list: List of token dictionaries
|
||||
"""
|
||||
try:
|
||||
with open(".well-known/wallets/.tokens") as file:
|
||||
return json.load(file)
|
||||
except Exception as e:
|
||||
print(f"Error loading tokens: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_coin_names():
|
||||
"""
|
||||
Get coin names with caching.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of coin names
|
||||
"""
|
||||
try:
|
||||
with open(".well-known/wallets/.coins") as file:
|
||||
return json.load(file)
|
||||
except Exception as e:
|
||||
print(f"Error loading coin names: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_wallet_domains():
|
||||
"""
|
||||
Get wallet domains with caching.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of wallet domains
|
||||
"""
|
||||
try:
|
||||
if os.path.isfile(".well-known/wallets/.domains"):
|
||||
with open(".well-known/wallets/.domains") as file:
|
||||
return json.load(file)
|
||||
except Exception as e:
|
||||
print(f"Error loading domains: {e}")
|
||||
return {}
|
||||
34
cleanSite.py
@@ -1,29 +1,37 @@
|
||||
import os
|
||||
|
||||
# Read all files in the templates directory
|
||||
for file in os.listdir('templates'):
|
||||
|
||||
def cleanSite(path: str):
|
||||
# Check if the file is sitemap.xml
|
||||
if file == 'sitemap.xml':
|
||||
if path.endswith("sitemap.xml"):
|
||||
# Open the file
|
||||
with open('templates/sitemap.xml', 'r') as f:
|
||||
with open(path, "r") as f:
|
||||
# Read the content
|
||||
content = f.read()
|
||||
# Replace all .html with empty string
|
||||
content = content.replace('.html', '')
|
||||
content = content.replace(".html", "")
|
||||
# Write the content back to the file
|
||||
with open('templates/sitemap.xml', 'w') as f:
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
# Skip the file
|
||||
continue
|
||||
|
||||
return
|
||||
|
||||
# If the file is not an html file, skip it
|
||||
if not file.endswith('.html'):
|
||||
continue
|
||||
if not path.endswith(".html"):
|
||||
if os.path.isdir(path):
|
||||
for file in os.listdir(path):
|
||||
cleanSite(path + "/" + file)
|
||||
|
||||
return
|
||||
|
||||
# Open the file
|
||||
with open('templates/' + file, 'r') as f:
|
||||
with open(path, "r") as f:
|
||||
# Read and remove all .html
|
||||
content = f.read().replace('.html"', '"')
|
||||
# Write the cleaned content back to the file
|
||||
with open('templates/' + file, 'w') as f:
|
||||
f.write(content)
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
for file in os.listdir("templates"):
|
||||
cleanSite("templates/" + file)
|
||||
|
||||
148
curl.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from flask import render_template
|
||||
from tools import getAddress, get_tools_data, getClientIP
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from blueprints.spotify import get_spotify_track
|
||||
from cache_helper import get_git_latest_activity, get_projects as get_projects_cached
|
||||
|
||||
|
||||
MAX_WIDTH = 80
|
||||
|
||||
|
||||
def clean_path(path: str):
|
||||
path = path.strip("/ ").lower()
|
||||
# Strip any .html extension
|
||||
if path.endswith(".html"):
|
||||
path = path[:-5]
|
||||
|
||||
# If the path is empty, set it to "index"
|
||||
if path == "":
|
||||
path = "index"
|
||||
return path
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_header():
|
||||
with open("templates/header.ascii", "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def get_current_project():
|
||||
git = get_git_latest_activity()
|
||||
repo_name = git["repo"]["name"].lower()
|
||||
repo_description = git["repo"]["description"]
|
||||
if not repo_description:
|
||||
return f"[1;36m{repo_name}[0m"
|
||||
return f"[1;36m{repo_name}[0m - [1m{repo_description}[0m"
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def get_projects():
|
||||
projects_data = get_projects_cached(limit=5)
|
||||
projects = ""
|
||||
for project in projects_data:
|
||||
projects += f"""[1m{project["name"]}[0m - {project["description"] if project["description"] else "No description"}
|
||||
{project["html_url"]}
|
||||
|
||||
"""
|
||||
return projects
|
||||
|
||||
|
||||
def curl_response(request):
|
||||
# Check if <path>.ascii exists
|
||||
path = clean_path(request.path)
|
||||
|
||||
# Handle special cases
|
||||
if path == "index":
|
||||
# Get current project
|
||||
return (
|
||||
render_template(
|
||||
"index.ascii",
|
||||
repo=get_current_project(),
|
||||
ip=getClientIP(request),
|
||||
spotify=get_spotify_track(),
|
||||
),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
if path == "projects":
|
||||
# Get projects
|
||||
return (
|
||||
render_template(
|
||||
"projects.ascii", header=get_header(), projects=get_projects()
|
||||
),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
if path == "donate":
|
||||
# Get donation info
|
||||
return (
|
||||
render_template(
|
||||
"donate.ascii",
|
||||
header=get_header(),
|
||||
HNS=getAddress("HNS"),
|
||||
BTC=getAddress("BTC"),
|
||||
SOL=getAddress("SOL"),
|
||||
ETH=getAddress("ETH"),
|
||||
),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
if path == "donate/more":
|
||||
coinList = os.listdir(".well-known/wallets")
|
||||
coinList = [file for file in coinList if file[0] != "."]
|
||||
coinList.sort()
|
||||
return (
|
||||
render_template("donate_more.ascii", header=get_header(), coins=coinList),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
# For other donation pages, fall back to ascii if it exists
|
||||
if path.startswith("donate/"):
|
||||
coin = path.split("/")[1]
|
||||
address = getAddress(coin)
|
||||
if address != "":
|
||||
return (
|
||||
render_template(
|
||||
"donate_coin.ascii",
|
||||
header=get_header(),
|
||||
coin=coin.upper(),
|
||||
address=address,
|
||||
),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
if path == "tools":
|
||||
tools = get_tools_data()
|
||||
return (
|
||||
render_template("tools.ascii", header=get_header(), tools=tools),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
if os.path.exists(f"templates/{path}.ascii"):
|
||||
return (
|
||||
render_template(f"{path}.ascii", header=get_header()),
|
||||
200,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
|
||||
# Fallback to html if it exists
|
||||
if os.path.exists(f"templates/{path}.html"):
|
||||
return render_template(f"{path}.html")
|
||||
|
||||
# Return curl error page
|
||||
error = {
|
||||
"code": 404,
|
||||
"message": "The requested resource was not found on this server.",
|
||||
}
|
||||
return (
|
||||
render_template("error.ascii", header=get_header(), error=error),
|
||||
404,
|
||||
{"Content-Type": "text/plain; charset=utf-8"},
|
||||
)
|
||||
27
data/blog/Fingertip_on_Linux_Mint.md
Normal file
@@ -0,0 +1,27 @@
|
||||
[View video tutorial](https://cloud.woodburn.au/s/n7Q3k7QyEnwygjX)
|
||||
|
||||
|
||||
Install prerequisites:
|
||||
```bash
|
||||
sudo apt install libfuse2 libunbound-dev
|
||||
```
|
||||
|
||||
Download latest release AppImage from SANE version of Fingertip:
|
||||
[Fingertip Github Repo](https://github.com/randomlogin/fingertip)
|
||||
|
||||
Make the AppImage executable:
|
||||
```bash
|
||||
chmod +x Fingertip-*.AppImage
|
||||
```
|
||||
Run the AppImage:
|
||||
```bash
|
||||
./Fingertip-*.AppImage
|
||||
```
|
||||
|
||||
You should see the fingertip notification icon in the system tray, right-click and select Options > Help. This should open a browser window with the Fingertip status page.
|
||||
Go to the "Manual Setup" page, download the certificate and copy the proxy pac URL.
|
||||
|
||||
Open the system settings > Network > Network Proxy. Set the Method to Automatic and paste in the URL.
|
||||
|
||||
Next we need to import the certificate authority into your preferred browser.
|
||||
You can usually just open the settings in the browser and search for "Certificates". Then import the certificate into the "Authorities" tab and make sure you select the option to trust it for identifying websites.
|
||||
28
data/blog/Nameserver_Setup_On_BobWallet.md
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
Setting up a Nameserver for your domains held in BobWallet is needed in order to use your domains for websites or other services.
|
||||
This guide will walk you through the process of setting up a nameserver using the BobWallet app.
|
||||
|
||||
|
||||
<br>
|
||||
#### Prerequisites
|
||||
* [BobWallet](https://bobwallet.io) with at least 1 domain
|
||||
|
||||
Once you have your domain in BobWallet, you can set up a nameserver using the HNSAU service. This is a free service that allows you to create a nameserver for your domains.
|
||||
1. Create an account at [HNSAU's free Nameserver service](https://domains.hns.au)
|
||||
2. In the Add Site section, enter your domain name. Ensure you don't include any protocols (http:// or https://), subdomains (www.), or trailing slashes (/).
|
||||
3. You should now see your domain listed in the External Domains section.
|
||||
4. Click on the manage button next to the domain name to view its details. Keep this page open, as you will need to copy the nameserver and DS info later.
|
||||
5. In BobWallet, go to Domain Manger and select the domain you want to set up a nameserver for.
|
||||
6. In the Records section for the domain, remove any existing records with TYPE NS or DS.
|
||||
7. Click on the Add Record button and select the TYPE NS. Add the NS value from the HNSAU page. Make sure you include the trailing dot (.) at the end of the nameserver. Repeat this for all the Nameservers listed on the HNSAU page.
|
||||
- ns1.australia.
|
||||
- ns2.australia.
|
||||
8. Click on the Add Record button again and select the TYPE DS. Add the DS value from the DNSSEC section in HNSAU. This DS value is unique to each domain and is used to verify the authenticity of the nameserver.
|
||||
9. Submit the changes and wait for the DNS records to propagate onchain. This will take up to 7 hrs (depending on the next tree update).
|
||||
10. You can now use the HNSAU nameserver to point your domain to any website or service.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[View demonstration video](https://youtu.be/Ong8A7FDH24)
|
||||
53
data/blog/Software_I_Use.md
Normal file
@@ -0,0 +1,53 @@
|
||||
G'day,
|
||||
Just thought it might be useful to write down some of the software I use regularly. I've no clue if you'll find any useful :)
|
||||
|
||||
For a more complete list, check out [/tools](/tools)
|
||||
|
||||
<br>
|
||||
## Overview
|
||||
OS: Arch Linux | Because it is quick to update and has all the latest tools I can play with
|
||||
DE: Hyprland | Feel free to check out my dotfiles if you're interested
|
||||
Shell: ZSH
|
||||
|
||||
<br>
|
||||
## Desktop Applications
|
||||
|
||||
[Obsidian](https://obsidian.md/) | Note taking app that stores everything in Markdown files
|
||||
[Alacritty](https://alacritty.org/) | Terminal emulator
|
||||
[Brave](https://brave.com/) | Browser with ad blocker built in
|
||||
[VSCode](https://code.visualstudio.com/) | Yeah its heavy but I'm used to it
|
||||
|
||||
<br>
|
||||
## Terminal Tools
|
||||
|
||||
[Zellij](https://zellij.dev/) | Easy to use terminal multiplexer
|
||||
[Fx](https://fx.wtf/) | JSON parser with pretty colours. Similar to jq
|
||||
[Zoxide](https://github.com/ajeetdsouza/zoxide) | cd but with fuzzy matching and other cool features
|
||||
[Atuin](https://atuin.sh/) | Terminal history with fuzzy search
|
||||
[Tmate](https://tmate.io/) | Terminal sharing. Useful when troubleshooting isses for remote users
|
||||
[Eza](https://eza.rocks/) | Like ls but pretty
|
||||
[Tre](https://github.com/dduan/tre) | Like tree but pretty
|
||||
[Bat](https://github.com/sharkdp/bat) | Like cat but pretty. Syntax highlighting, line numbers, search, git integration and more
|
||||
[Oh My ZSH](https://ohmyz.sh/) | Shell customization and plugins
|
||||
|
||||
<br>
|
||||
## Server Management
|
||||
|
||||
[Proxmox](https://proxmox.com/en/) | Virtualization manager for my baremetal server
|
||||
[Portainer](https://www.portainer.io/) | Docker container manager
|
||||
[Coolify](https://coolify.io/) | Open source alternative to heroku. I use it to host a lot of different services
|
||||
[Opnsense](https://opnsense.org/) | Firewall and router
|
||||
[Nginx Proxy Manager](https://nginxproxymanager.com/) | Reverse proxy manager with a nice UI
|
||||
[Tailscale](https://tailscale.com/) | VPN to let me access my network from anywhere
|
||||
|
||||
<br>
|
||||
## Self-Hosting Services
|
||||
[Authentik](https://goauthentik.io/) | Identity provider for single sign on
|
||||
[Gitea](https://gitea.io/) | Git hosting service
|
||||
[Nextcloud](https://nextcloud.com/) | Think Dropbox but self hosted
|
||||
[Umami](https://umami.is/) | Self hosted web analytics
|
||||
[Uptime Kuma](https://uptime.kuma.pet/) | Self hosted status page and monitoring tool
|
||||
[PhotoPrism](https://photoprism.app/) | Self hosted photo management tool
|
||||
[FreeScout](https://freescout.net/) | Self hosted email dashboard
|
||||
[Transfer.sh](https://upload.woodburn.au/) | Self hosted file sharing service
|
||||
|
||||
BIN
data/resume.pdf
Normal file
BIN
data/resume_support.pdf
Normal file
@@ -10,7 +10,8 @@
|
||||
"url": "https://domains.hns.au",
|
||||
"img": "/assets/img/external/HNSAU.webp",
|
||||
"name": "HNSAU Registry",
|
||||
"description": "An easy to use DNS provider and domain reselling platform"
|
||||
"description": "An easy to use DNS provider and domain reselling platform",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"url": "https://hns.au",
|
||||
@@ -22,7 +23,8 @@
|
||||
"url": "https://hnshosting.au",
|
||||
"img": "/favicon.png",
|
||||
"name": "HNS Hosting",
|
||||
"description": "Simple Wordpress hosting for Handshake domains with builtin SSL using DANE"
|
||||
"description": "Simple Wordpress hosting for Handshake domains with builtin SSL using DANE",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"url": "https://firewallet.au",
|
||||
@@ -34,7 +36,8 @@
|
||||
"url": "https://shakecities.com",
|
||||
"img": "/assets/img/external/HNSW.png",
|
||||
"name": "ShakeCities",
|
||||
"description": "A single page website creator where each user's page on their free HNS domain"
|
||||
"description": "A single page website creator where each user's page on their free HNS domain",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"url": "https://git.woodburn.au",
|
||||
@@ -46,7 +49,8 @@
|
||||
"url": "https://linkr",
|
||||
"img": "/favicon.png",
|
||||
"name": "LINKR/",
|
||||
"description": "A free link shortener with a Handshake TLD and using DNS for authentication"
|
||||
"description": "A free link shortener with a Handshake TLD and using DNS for authentication",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"url": "https://faucet.woodburn.au",
|
||||
@@ -59,5 +63,23 @@
|
||||
"img": "/assets/img/external/HNS.png",
|
||||
"name": "HNS DoH",
|
||||
"description": "A DNS over HTTPS resolver for Handshake domains"
|
||||
},
|
||||
{
|
||||
"url": "https://ipfs.hnsproxy.au",
|
||||
"img": "https://ipfs.hnsproxy.au/fireportal.png",
|
||||
"name": "FirePortal",
|
||||
"description": "A Handshake domain IPFS gateway that allows you to access IPFS content using Handshake domains"
|
||||
},
|
||||
{
|
||||
"url": "https://hsd.hns.au/",
|
||||
"img": "/favicon.png",
|
||||
"name": "Fire HSD",
|
||||
"description": "A free public API for Handshake (HSD)"
|
||||
},
|
||||
{
|
||||
"url": "https://time.c.woodburn.au/",
|
||||
"img": "/favicon.png",
|
||||
"name": "Timezone Converter",
|
||||
"description": "A simple site and API for converting timezones"
|
||||
}
|
||||
]
|
||||
171
data/tools.json
Normal file
@@ -0,0 +1,171 @@
|
||||
[
|
||||
{
|
||||
"name":"Obsidian",
|
||||
"type":"Desktop Applications",
|
||||
"url":"https://obsidian.md/",
|
||||
"description":"Note taking app that stores everything in Markdown files"
|
||||
},
|
||||
{
|
||||
"name": "Alacritty",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://alacritty.org/",
|
||||
"description": "A cross-platform, GPU-accelerated terminal emulator"
|
||||
},
|
||||
{
|
||||
"name": "Brave",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://brave.com/",
|
||||
"description": "Privacy-focused web browser"
|
||||
},
|
||||
{
|
||||
"name": "VSCode",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://code.visualstudio.com/",
|
||||
"description": "Source-code editor developed by Microsoft"
|
||||
},
|
||||
{
|
||||
"name": "Vesktop",
|
||||
"type": "Desktop Applications",
|
||||
"url": "https://vesktop.dev/",
|
||||
"description": "Vesktop is a customizable and privacy friendly Discord desktop app!"
|
||||
},
|
||||
{
|
||||
"name": "Zellij",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://zellij.dev/",
|
||||
"description": "A terminal workspace and multiplexer",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/10"
|
||||
},
|
||||
{
|
||||
"name": "Fx",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://fx.wtf/",
|
||||
"description": "A command-line JSON viewer and processor",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/4"
|
||||
},
|
||||
{
|
||||
"name": "Zoxide",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://github.com/ajeetdsouza/zoxide",
|
||||
"description": "cd but with fuzzy matching and other cool features",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/5"
|
||||
},
|
||||
{
|
||||
"name": "Atuin",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://atuin.sh/",
|
||||
"description": "A next-generation shell history manager",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/6"
|
||||
},
|
||||
{
|
||||
"name": "Tmate",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://tmate.io/",
|
||||
"description": "Instant terminal sharing",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/7"
|
||||
},
|
||||
{
|
||||
"name": "Eza",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://eza.rocks/",
|
||||
"description": "A modern replacement for 'ls'",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/8"
|
||||
},
|
||||
{
|
||||
"name": "Bat",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://github.com/sharkdp/bat",
|
||||
"description": "A cat clone with syntax highlighting and Git integration",
|
||||
"demo": "https://asciinema.c.woodburn.au/a/9"
|
||||
},
|
||||
{
|
||||
"name": "Oh My Zsh",
|
||||
"type": "Terminal Tools",
|
||||
"url": "https://ohmyz.sh/",
|
||||
"description": "A delightful community-driven framework for managing your Zsh configuration"
|
||||
},
|
||||
{
|
||||
"name": "Proxmox",
|
||||
"type": "Server Management",
|
||||
"url": "https://www.proxmox.com/en",
|
||||
"description": "Open-source server virtualization management solution"
|
||||
},
|
||||
{
|
||||
"name": "Portainer",
|
||||
"type": "Server Management",
|
||||
"url": "https://www.portainer.io/",
|
||||
"description": "Lightweight management UI which allows you to easily manage your Docker containers"
|
||||
},
|
||||
{
|
||||
"name": "Coolify",
|
||||
"type": "Server Management",
|
||||
"url": "https://coolify.io/",
|
||||
"description": "An open-source self-hosted Heroku alternative"
|
||||
},
|
||||
{
|
||||
"name": "OpnSense",
|
||||
"type": "Server Management",
|
||||
"url": "https://opnsense.org/",
|
||||
"description": "Open source, easy-to-use and easy-to-build FreeBSD based firewall and routing platform"
|
||||
},
|
||||
{
|
||||
"name": "Nginx Proxy Manager",
|
||||
"type": "Server Management",
|
||||
"url": "https://nginxproxymanager.com/",
|
||||
"description": "A powerful yet easy to use web interface for managing Nginx proxy hosts"
|
||||
},
|
||||
{
|
||||
"name": "Tailscale",
|
||||
"type": "Server Management",
|
||||
"url": "https://tailscale.com/",
|
||||
"description": "A zero-config VPN that just works"
|
||||
},
|
||||
{
|
||||
"name": "Authentik",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://goauthentik.io/",
|
||||
"description": "An open-source identity provider focused on flexibility and ease of use"
|
||||
},
|
||||
{
|
||||
"name": "Uptime Kuma",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://uptime.kuma.pet/",
|
||||
"description": "A fancy self-hosted monitoring tool"
|
||||
},
|
||||
{
|
||||
"name": "Gitea",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://about.gitea.com/",
|
||||
"description": "A painless self-hosted Git service"
|
||||
},
|
||||
{
|
||||
"name": "Nextcloud",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://nextcloud.com/",
|
||||
"description": "A suite of client-server software for creating and using file hosting services"
|
||||
},
|
||||
{
|
||||
"name": "Umami",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://umami.is/",
|
||||
"description": "A simple, fast, privacy-focused alternative to Google Analytics"
|
||||
},
|
||||
{
|
||||
"name": "PhotoPrism",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://photoprism.app/",
|
||||
"description": "AI-powered app for browsing, organizing & sharing your photo collection"
|
||||
},
|
||||
{
|
||||
"name": "FreeScout",
|
||||
"type": "Self-Hosting Services",
|
||||
"url": "https://freescout.net/",
|
||||
"description": "Self hosted email dashboard"
|
||||
},
|
||||
{
|
||||
"name": "Vaultwarden",
|
||||
"type": "Miscellaneous",
|
||||
"url": "https://github.com/dani-garcia/vaultwarden",
|
||||
"description": "Password manager server implementation compatible with Bitwarden clients"
|
||||
}
|
||||
]
|
||||
96
mail.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import smtplib
|
||||
import re
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.utils import formataddr
|
||||
from flask import jsonify
|
||||
import os
|
||||
|
||||
|
||||
# This is used to send emails via API
|
||||
# The process should be something like this
|
||||
# curl --request POST \
|
||||
# --url https://nathan.c.woodburn.au/api/email \
|
||||
# --header 'Content-Type: application/json' \
|
||||
# --data '{
|
||||
# "key":"api-key",
|
||||
# "to": "recipient@nathan.woodburn.au",
|
||||
# "from": "sender@nathan.woodburn.au",
|
||||
# "sender":"Nathan.Woodburn/",
|
||||
# "subject":"Test email from api",
|
||||
# "body":"G'\''day\nThis is a test email from my website api\n\nRegards,\nNathan.Woodburn/"
|
||||
# }'
|
||||
|
||||
|
||||
def validateSender(email):
|
||||
domains = os.getenv("EMAIL_DOMAINS")
|
||||
if not domains:
|
||||
return False
|
||||
|
||||
domains = domains.split(",")
|
||||
for domain in domains:
|
||||
if re.match(r".+@" + domain, email):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def sendEmail(data):
|
||||
fromEmail = "noreply@woodburn.au"
|
||||
if "from" in data:
|
||||
fromEmail = data["from"]
|
||||
|
||||
if not validateSender(fromEmail):
|
||||
return jsonify({"status": 400, "message": "Bad request 'from' email invalid"})
|
||||
|
||||
if "to" not in data:
|
||||
return jsonify({"status": 400, "message": "Bad request 'to' json data missing"})
|
||||
to = data["to"]
|
||||
|
||||
if "subject" not in data:
|
||||
return jsonify(
|
||||
{"status": 400, "message": "Bad request 'subject' json data missing"}
|
||||
)
|
||||
subject = data["subject"]
|
||||
|
||||
if "body" not in data:
|
||||
return jsonify(
|
||||
{"status": 400, "message": "Bad request 'body' json data missing"}
|
||||
)
|
||||
body = data["body"]
|
||||
|
||||
if not re.match(r"[^@]+@[^@]+\.[^@]+", to):
|
||||
raise ValueError("Invalid recipient email address.")
|
||||
|
||||
if not subject:
|
||||
raise ValueError("Subject cannot be empty.")
|
||||
|
||||
if not body:
|
||||
raise ValueError("Body cannot be empty.")
|
||||
|
||||
fromName = "Nathan Woodburn"
|
||||
if "sender" in data:
|
||||
fromName = data["sender"]
|
||||
|
||||
# Create the email message
|
||||
msg = MIMEMultipart()
|
||||
msg["From"] = formataddr((fromName, fromEmail))
|
||||
msg["To"] = to
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(body, "plain"))
|
||||
|
||||
# Sending the email
|
||||
try:
|
||||
host = os.getenv("EMAIL_SMTP")
|
||||
user = os.getenv("EMAIL_USER")
|
||||
password = os.getenv("EMAIL_PASS")
|
||||
if host is None or user is None or password is None:
|
||||
return jsonify({"status": 500, "error": "Email server not configured"})
|
||||
|
||||
with smtplib.SMTP_SSL(host, 465) as server:
|
||||
server.login(user, password)
|
||||
server.sendmail(fromEmail, to, msg.as_string())
|
||||
print("Email sent successfully.")
|
||||
return jsonify({"status": 200, "message": "Send email successfully"})
|
||||
except Exception as e:
|
||||
return jsonify({"status": 500, "error": "Sending email failed", "exception": e})
|
||||
32
main.py
@@ -1,12 +1,6 @@
|
||||
import time
|
||||
from flask import Flask
|
||||
from server import app
|
||||
import server
|
||||
from gunicorn.app.base import BaseApplication
|
||||
import os
|
||||
import dotenv
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
class GunicornApp(BaseApplication):
|
||||
@@ -17,15 +11,16 @@ class GunicornApp(BaseApplication):
|
||||
|
||||
def load_config(self):
|
||||
for key, value in self.options.items():
|
||||
if key in self.cfg.settings and value is not None:
|
||||
self.cfg.set(key.lower(), value)
|
||||
if key in self.cfg.settings and value is not None: # type: ignore
|
||||
self.cfg.set(key.lower(), value) # type: ignore
|
||||
|
||||
def load(self):
|
||||
return self.application
|
||||
|
||||
if __name__ == '__main__':
|
||||
workers = os.getenv('WORKERS')
|
||||
threads = os.getenv('THREADS')
|
||||
|
||||
if __name__ == "__main__":
|
||||
workers = os.getenv("WORKERS")
|
||||
threads = os.getenv("THREADS")
|
||||
if workers is None:
|
||||
workers = 1
|
||||
if threads is None:
|
||||
@@ -33,10 +28,17 @@ if __name__ == '__main__':
|
||||
workers = int(workers)
|
||||
threads = int(threads)
|
||||
options = {
|
||||
'bind': '0.0.0.0:5000',
|
||||
'workers': workers,
|
||||
'threads': threads,
|
||||
"bind": "0.0.0.0:5000",
|
||||
"workers": workers,
|
||||
"threads": threads,
|
||||
}
|
||||
gunicorn_app = GunicornApp(app, options)
|
||||
print('Starting server with ' + str(workers) + ' workers and ' + str(threads) + ' threads', flush=True)
|
||||
print(
|
||||
"Starting server with "
|
||||
+ str(workers)
|
||||
+ " workers and "
|
||||
+ str(threads)
|
||||
+ " threads",
|
||||
flush=True,
|
||||
)
|
||||
gunicorn_app.run()
|
||||
|
||||
26
pwa/manifest.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"short_name": "Nathan.Woodburn/",
|
||||
"name": "Nathan.Woodburn/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://nathan.woodburn.au/assets/img/favicon/android-chrome-192x192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "https://nathan.woodburn.au/assets/img/favicon/android-chrome-512x512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": "https://nathan.woodburn.au",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"display": "fullscreen",
|
||||
"orientation": "portrait",
|
||||
"id": "nathanwoodburn",
|
||||
"description": "Nathan.Woodburn/",
|
||||
"scope": "https://nathan.woodburn.au",
|
||||
"dir": "ltr",
|
||||
"lang": "en"
|
||||
}
|
||||
37
pwa/sw.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// This is the service worker with the combined offline experience (Offline page + Offline copy of pages)
|
||||
|
||||
const CACHE = "pwabuilder-offline-page";
|
||||
|
||||
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
|
||||
|
||||
const PRECACHE_ASSETS = [
|
||||
'/',
|
||||
'/404',
|
||||
'/assets/bootstrap/css/bootstrap.min.css',
|
||||
'https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap',
|
||||
'https://fonts.googleapis.com/css?family=Cabin:700&display=swap',
|
||||
'https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap',
|
||||
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700',
|
||||
'/assets/css/styles.min.css',
|
||||
'/assets/css/404.min.css',
|
||||
'/assets/css/profile.min.css',
|
||||
'/assets/bootstrap/js/bootstrap.min.js',
|
||||
'/assets/js/script.min.js',
|
||||
'/assets/js/404.min.js',
|
||||
'/assets/img/favicon/favicon-16x16.png',
|
||||
'/assets/img/favicon/android-chrome-192x192.png'
|
||||
]
|
||||
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('install', async (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE)
|
||||
.then((cache) => cache.addAll(PRECACHE_ASSETS))
|
||||
);
|
||||
});
|
||||
31
pyproject.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[project]
|
||||
name = "nathanwoodburn-github-io"
|
||||
version = "1.1.0"
|
||||
description = "Nathan.Woodburn Personal Website"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"ansi2html>=1.9.2",
|
||||
"beautifulsoup4>=4.14.2",
|
||||
"cachetools>=6.2.1",
|
||||
"cloudflare>=4.3.1",
|
||||
"flask>=3.1.2",
|
||||
"flask-cors>=6.0.1",
|
||||
"gunicorn>=23.0.0",
|
||||
"markdown>=3.9",
|
||||
"pillow>=12.0.0",
|
||||
"pydantic>=2.12.3",
|
||||
"pygments>=2.19.2",
|
||||
"python-dateutil>=2.9.0.post0",
|
||||
"python-dotenv>=1.2.1",
|
||||
"qrcode>=8.2",
|
||||
"requests>=2.32.5",
|
||||
"solana>=0.36.9",
|
||||
"solders>=0.26.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pre-commit>=4.4.0",
|
||||
"ruff>=0.14.5",
|
||||
]
|
||||
487
requirements.txt
@@ -1,12 +1,475 @@
|
||||
flask
|
||||
Flask-Cors
|
||||
python-dotenv
|
||||
gunicorn
|
||||
requests
|
||||
cloudflare == 3.0.1
|
||||
qrcode
|
||||
Pillow
|
||||
ansi2html
|
||||
cachetools
|
||||
solana == 0.34.2
|
||||
solders == 0.21.0
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --frozen --output-file=requirements.txt
|
||||
annotated-types==0.7.0 \
|
||||
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
|
||||
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
|
||||
# via pydantic
|
||||
ansi2html==1.9.2 \
|
||||
--hash=sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5 \
|
||||
--hash=sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5
|
||||
# via nathanwoodburn-github-io
|
||||
anyio==4.11.0 \
|
||||
--hash=sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc \
|
||||
--hash=sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4
|
||||
# via
|
||||
# cloudflare
|
||||
# httpx
|
||||
beautifulsoup4==4.14.2 \
|
||||
--hash=sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e \
|
||||
--hash=sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515
|
||||
# via nathanwoodburn-github-io
|
||||
blinker==1.9.0 \
|
||||
--hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \
|
||||
--hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc
|
||||
# via flask
|
||||
cachetools==6.2.1 \
|
||||
--hash=sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701 \
|
||||
--hash=sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201
|
||||
# via nathanwoodburn-github-io
|
||||
certifi==2025.10.5 \
|
||||
--hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \
|
||||
--hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
cfgv==3.4.0 \
|
||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||
# via pre-commit
|
||||
charset-normalizer==3.4.4 \
|
||||
--hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \
|
||||
--hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \
|
||||
--hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \
|
||||
--hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \
|
||||
--hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \
|
||||
--hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \
|
||||
--hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \
|
||||
--hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \
|
||||
--hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \
|
||||
--hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \
|
||||
--hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \
|
||||
--hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \
|
||||
--hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \
|
||||
--hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \
|
||||
--hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \
|
||||
--hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \
|
||||
--hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \
|
||||
--hash=sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a \
|
||||
--hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \
|
||||
--hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \
|
||||
--hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \
|
||||
--hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \
|
||||
--hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \
|
||||
--hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \
|
||||
--hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \
|
||||
--hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \
|
||||
--hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \
|
||||
--hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \
|
||||
--hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \
|
||||
--hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \
|
||||
--hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \
|
||||
--hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \
|
||||
--hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \
|
||||
--hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9
|
||||
# via requests
|
||||
click==8.3.0 \
|
||||
--hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \
|
||||
--hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4
|
||||
# via flask
|
||||
cloudflare==4.3.1 \
|
||||
--hash=sha256:6927135a5ee5633d6e2e1952ca0484745e933727aeeb189996d2ad9d292071c6 \
|
||||
--hash=sha256:b1e1c6beeb8d98f63bfe0a1cba874fc4e22e000bcc490544f956c689b3b5b258
|
||||
# via nathanwoodburn-github-io
|
||||
colorama==0.4.6 ; sys_platform == 'win32' \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via
|
||||
# click
|
||||
# qrcode
|
||||
construct==2.10.68 \
|
||||
--hash=sha256:7b2a3fd8e5f597a5aa1d614c3bd516fa065db01704c72a1efaaeec6ef23d8b45
|
||||
# via construct-typing
|
||||
construct-typing==0.6.2 \
|
||||
--hash=sha256:948e998cfc003681dc34f2d071c3a688cf35b805cbe107febbc488ef967ccba1 \
|
||||
--hash=sha256:ebea6989ac622d0c4eb457092cef0c7bfbcfa110bd018670fea7064d0bc09e47
|
||||
# via solana
|
||||
distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
distro==1.9.0 \
|
||||
--hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \
|
||||
--hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2
|
||||
# via cloudflare
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
# via virtualenv
|
||||
flask==3.1.2 \
|
||||
--hash=sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87 \
|
||||
--hash=sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c
|
||||
# via
|
||||
# flask-cors
|
||||
# nathanwoodburn-github-io
|
||||
flask-cors==6.0.1 \
|
||||
--hash=sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c \
|
||||
--hash=sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db
|
||||
# via nathanwoodburn-github-io
|
||||
gunicorn==23.0.0 \
|
||||
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
|
||||
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
|
||||
# via nathanwoodburn-github-io
|
||||
h11==0.16.0 \
|
||||
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
||||
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
||||
# via httpcore
|
||||
httpcore==1.0.9 \
|
||||
--hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
|
||||
--hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
|
||||
# via httpx
|
||||
httpx==0.28.1 \
|
||||
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
||||
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
||||
# via
|
||||
# cloudflare
|
||||
# solana
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
# via pre-commit
|
||||
idna==3.11 \
|
||||
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
|
||||
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
itsdangerous==2.2.0 \
|
||||
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
|
||||
--hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
|
||||
# via flask
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
# via flask
|
||||
jsonalias==0.1.1 \
|
||||
--hash=sha256:64f04d935397d579fc94509e1fcb6212f2d081235d9d6395bd10baedf760a769 \
|
||||
--hash=sha256:a56d2888e6397812c606156504e861e8ec00e188005af149f003c787db3d3f18
|
||||
# via solders
|
||||
markdown==3.9 \
|
||||
--hash=sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280 \
|
||||
--hash=sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a
|
||||
# via nathanwoodburn-github-io
|
||||
markupsafe==3.0.3 \
|
||||
--hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
|
||||
--hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
|
||||
--hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
|
||||
--hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
|
||||
--hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
|
||||
--hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
|
||||
--hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
|
||||
--hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
|
||||
--hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
|
||||
--hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
|
||||
--hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
|
||||
--hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
|
||||
--hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
|
||||
--hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
|
||||
--hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
|
||||
--hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
|
||||
--hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
|
||||
--hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
|
||||
--hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
|
||||
--hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
|
||||
--hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
|
||||
--hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
|
||||
--hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
|
||||
--hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
|
||||
--hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
|
||||
--hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
|
||||
--hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
|
||||
--hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
|
||||
--hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
|
||||
--hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
|
||||
--hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
|
||||
--hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
|
||||
--hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
|
||||
--hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
|
||||
--hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
|
||||
--hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
|
||||
--hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
|
||||
--hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
|
||||
--hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
|
||||
--hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
|
||||
--hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
|
||||
--hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
|
||||
--hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
|
||||
--hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
|
||||
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
||||
# via
|
||||
# flask
|
||||
# jinja2
|
||||
# werkzeug
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
# via pre-commit
|
||||
packaging==25.0 \
|
||||
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
|
||||
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
|
||||
# via gunicorn
|
||||
pillow==12.0.0 \
|
||||
--hash=sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643 \
|
||||
--hash=sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e \
|
||||
--hash=sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6 \
|
||||
--hash=sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b \
|
||||
--hash=sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399 \
|
||||
--hash=sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad \
|
||||
--hash=sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47 \
|
||||
--hash=sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b \
|
||||
--hash=sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52 \
|
||||
--hash=sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d \
|
||||
--hash=sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a \
|
||||
--hash=sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9 \
|
||||
--hash=sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098 \
|
||||
--hash=sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 \
|
||||
--hash=sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b \
|
||||
--hash=sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01 \
|
||||
--hash=sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca \
|
||||
--hash=sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e \
|
||||
--hash=sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27 \
|
||||
--hash=sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e \
|
||||
--hash=sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8 \
|
||||
--hash=sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a \
|
||||
--hash=sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3 \
|
||||
--hash=sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353 \
|
||||
--hash=sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee \
|
||||
--hash=sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b \
|
||||
--hash=sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a \
|
||||
--hash=sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7 \
|
||||
--hash=sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef \
|
||||
--hash=sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a \
|
||||
--hash=sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07 \
|
||||
--hash=sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4 \
|
||||
--hash=sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4 \
|
||||
--hash=sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe \
|
||||
--hash=sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6 \
|
||||
--hash=sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3 \
|
||||
--hash=sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9 \
|
||||
--hash=sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 \
|
||||
--hash=sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b \
|
||||
--hash=sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e \
|
||||
--hash=sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab \
|
||||
--hash=sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79 \
|
||||
--hash=sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2 \
|
||||
--hash=sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0 \
|
||||
--hash=sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925 \
|
||||
--hash=sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b \
|
||||
--hash=sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced \
|
||||
--hash=sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c \
|
||||
--hash=sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 \
|
||||
--hash=sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9 \
|
||||
--hash=sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1
|
||||
# via nathanwoodburn-github-io
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||
# via virtualenv
|
||||
pre-commit==4.4.0 \
|
||||
--hash=sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813 \
|
||||
--hash=sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15
|
||||
pydantic==2.12.3 \
|
||||
--hash=sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74 \
|
||||
--hash=sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf
|
||||
# via
|
||||
# cloudflare
|
||||
# nathanwoodburn-github-io
|
||||
pydantic-core==2.41.4 \
|
||||
--hash=sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89 \
|
||||
--hash=sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d \
|
||||
--hash=sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2 \
|
||||
--hash=sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af \
|
||||
--hash=sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d \
|
||||
--hash=sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e \
|
||||
--hash=sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894 \
|
||||
--hash=sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa \
|
||||
--hash=sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e \
|
||||
--hash=sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1 \
|
||||
--hash=sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da \
|
||||
--hash=sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025 \
|
||||
--hash=sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5 \
|
||||
--hash=sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d \
|
||||
--hash=sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac \
|
||||
--hash=sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746 \
|
||||
--hash=sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a \
|
||||
--hash=sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84 \
|
||||
--hash=sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12 \
|
||||
--hash=sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2 \
|
||||
--hash=sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e \
|
||||
--hash=sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a \
|
||||
--hash=sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad \
|
||||
--hash=sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4 \
|
||||
--hash=sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf \
|
||||
--hash=sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0 \
|
||||
--hash=sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2 \
|
||||
--hash=sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d \
|
||||
--hash=sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2 \
|
||||
--hash=sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d \
|
||||
--hash=sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02 \
|
||||
--hash=sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616 \
|
||||
--hash=sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced \
|
||||
--hash=sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1 \
|
||||
--hash=sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c \
|
||||
--hash=sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4 \
|
||||
--hash=sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab \
|
||||
--hash=sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564 \
|
||||
--hash=sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554
|
||||
# via pydantic
|
||||
pygments==2.19.2 \
|
||||
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
|
||||
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
|
||||
# via nathanwoodburn-github-io
|
||||
python-dateutil==2.9.0.post0 \
|
||||
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
|
||||
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
|
||||
# via nathanwoodburn-github-io
|
||||
python-dotenv==1.2.1 \
|
||||
--hash=sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6 \
|
||||
--hash=sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61
|
||||
# via nathanwoodburn-github-io
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
|
||||
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
|
||||
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \
|
||||
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
|
||||
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \
|
||||
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \
|
||||
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \
|
||||
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \
|
||||
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \
|
||||
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
|
||||
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \
|
||||
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \
|
||||
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
|
||||
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \
|
||||
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \
|
||||
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
|
||||
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \
|
||||
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
|
||||
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \
|
||||
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
|
||||
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
|
||||
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
|
||||
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
|
||||
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
|
||||
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
|
||||
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
|
||||
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
|
||||
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6
|
||||
# via pre-commit
|
||||
qrcode==8.2 \
|
||||
--hash=sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f \
|
||||
--hash=sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c
|
||||
# via nathanwoodburn-github-io
|
||||
requests==2.32.5 \
|
||||
--hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
|
||||
--hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
|
||||
# via nathanwoodburn-github-io
|
||||
ruff==0.14.5 \
|
||||
--hash=sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68 \
|
||||
--hash=sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78 \
|
||||
--hash=sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4 \
|
||||
--hash=sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4 \
|
||||
--hash=sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a \
|
||||
--hash=sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19 \
|
||||
--hash=sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7 \
|
||||
--hash=sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1 \
|
||||
--hash=sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621 \
|
||||
--hash=sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb \
|
||||
--hash=sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1 \
|
||||
--hash=sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367 \
|
||||
--hash=sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b \
|
||||
--hash=sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465 \
|
||||
--hash=sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f \
|
||||
--hash=sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594 \
|
||||
--hash=sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2 \
|
||||
--hash=sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151 \
|
||||
--hash=sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72
|
||||
six==1.17.0 \
|
||||
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
||||
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||
# via python-dateutil
|
||||
sniffio==1.3.1 \
|
||||
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
|
||||
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
|
||||
# via
|
||||
# anyio
|
||||
# cloudflare
|
||||
solana==0.36.9 \
|
||||
--hash=sha256:e05824f91f95abe5a687914976e8bc78986386156f2106108c696db998c3c542 \
|
||||
--hash=sha256:f702f6177337c67a982909ef54ef3abce5e795b8cd93edb045bedfa4d13c20c5
|
||||
# via nathanwoodburn-github-io
|
||||
solders==0.26.0 \
|
||||
--hash=sha256:057533892d6fa432c1ce1e2f5e3428802964666c10b57d3d1bcaab86295f046c \
|
||||
--hash=sha256:1b964efbd7c0b38aef3bf4293ea5938517ae649b9a23e7cd147d889931775aab \
|
||||
--hash=sha256:36e6a769c5298b887b7588edb171d93709a89302aef75913fe893d11c653739d \
|
||||
--hash=sha256:3e3973074c17265921c70246a17bcf80972c5b96a3e1ed7f5049101f11865092 \
|
||||
--hash=sha256:5466616610170aab08c627ae01724e425bcf90085bc574da682e9f3bd954900b \
|
||||
--hash=sha256:5946ec3f2a340afa9ce5c2b8ab628ae1dea2ad2235551b1297cafdd7e3e5c51a \
|
||||
--hash=sha256:59b52419452602f697e659199a25acacda8365971c376ef3c0687aecdd929e07 \
|
||||
--hash=sha256:9c1a0ef5daa1a05934af5fb6e7e32eab7c42cede406c80067fee006f461ffc4a \
|
||||
--hash=sha256:b3cc55b971ec6ed1b4466fa7e7e09eee9baba492b8cd9e3204e3e1a0c5a0c4aa
|
||||
# via
|
||||
# nathanwoodburn-github-io
|
||||
# solana
|
||||
soupsieve==2.8 \
|
||||
--hash=sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c \
|
||||
--hash=sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f
|
||||
# via beautifulsoup4
|
||||
typing-extensions==4.15.0 \
|
||||
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
||||
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
||||
# via
|
||||
# beautifulsoup4
|
||||
# cloudflare
|
||||
# construct-typing
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# solana
|
||||
# solders
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2 \
|
||||
--hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \
|
||||
--hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464
|
||||
# via pydantic
|
||||
urllib3==2.5.0 \
|
||||
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||
# via requests
|
||||
virtualenv==20.35.4 \
|
||||
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||
# via pre-commit
|
||||
websockets==15.0.1 \
|
||||
--hash=sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8 \
|
||||
--hash=sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375 \
|
||||
--hash=sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f \
|
||||
--hash=sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4 \
|
||||
--hash=sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22 \
|
||||
--hash=sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675 \
|
||||
--hash=sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151 \
|
||||
--hash=sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d \
|
||||
--hash=sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee \
|
||||
--hash=sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa \
|
||||
--hash=sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561 \
|
||||
--hash=sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931 \
|
||||
--hash=sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f
|
||||
# via solana
|
||||
werkzeug==3.1.3 \
|
||||
--hash=sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e \
|
||||
--hash=sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746
|
||||
# via
|
||||
# flask
|
||||
# flask-cors
|
||||
|
||||
53
templates/403.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/403">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/403">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/404.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>HTTP: <span>403</span></p>
|
||||
<div class="text-center">
|
||||
<div class="text-start" style="display: inline-block;"><code><em>this_page</em>.<em>found</em> = true;</code><code><span>if</span> (<em>this_page</em>.<em>readable</em>) {<br><span class="tab-space"></span><b>return</b> <em>this_page</em>;<br>} <span>else</span> {<br><span class="tab-space"></span><b>alert</b>('<i>This page is not readable!</i>');<br>}</code></div>
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/403.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/404">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/404">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,10 +29,12 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/404.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
@@ -43,6 +46,7 @@
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/404.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/about">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/about">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,12 +29,14 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/fixes.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
@@ -105,9 +108,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p style="margin-top: 1em;">Hi, I am Nathan Woodburn and I live in Canberra<br>I am currently studying at the Australian National University<br>I enjoy 3D printing and CAD<br>I code stuff with C#, Linux Bash and tons of other languages<br>I'm a co-founder of <a href="https://hns.au" target="_blank">Handshake Australia</a><br>I currently work for <a href="https://learn.namebase.io" target="_blank">Namebase</a><br><br></p><i class="fas fa-arrow-down" style="font-size: 50px;" onclick="slideout()"></i>
|
||||
<p style="margin-top: 1em;">Hi, I am Nathan Woodburn and I live in Canberra<br>I am currently studying at the Australian National University<br>I enjoy managing linux servers for my various projects<br>I code stuff with C#, Linux Bash and tons of other languages<br>I'm a co-founder of <a href="https://hns.au" target="_blank">Handshake Australia</a><br><br></p><i class="fas fa-arrow-down" style="font-size: 50px;" onclick="slideout()"></i>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/about.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
1
templates/assets/css/Social-Icons.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}@media (max-width:500px){img.profile{width:200px;margin-left:-100px}.profile-container{height:200px;margin-top:2em!important}}.social-div{display:flex;justify-content:center;align-items:center}.social-list-sml{display:flex;list-style:none;gap:1rem}.social-list{display:flex;list-style:none;gap:2.5rem}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link-sml a{text-decoration:none;width:3.5rem;height:3.5rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before,.social-link-sml a::before{content:"";position:absolute;width:100%;height:100%;background:var(--bg-color);z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link-sml:hover a::before,.social-link:hover a::before{scale:1 1}.icon-sml{font-size:1.5rem;color:#011827;transition:.5s;z-index:2}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}.social-link,.social-link-sml{--bg-color:#000}.social-link-sml.discord,.social-link.discord{--bg-color:#5865f2}.social-link-sml.mastodon,.social-link.mastodon{--bg-color:#6364ff}.social-link-sml.youtube,.social-link.youtube{--bg-color:#ff0000}.social-link-sml.signal,.social-link.signal{--bg-color:#365eb6}
|
||||
1
templates/assets/css/blog.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p{margin:auto!important}pre{line-height:125%}li{list-style:inside}.social-link{list-style:none!important}span.linenos,td.linenos .normal{color:inherit;background-color:transparent;padding-left:5px;padding-right:5px}span.linenos.special,td.linenos .special{color:#000;background-color:#ffffc0;padding-left:5px;padding-right:5px}.codehilite .hll{background-color:#ffc}.codehilite{background:#f8f8f8;color:#333;width:fit-content;margin:auto;padding:0 5px;border-radius:5px}.codehilite .c,.codehilite .c1,.codehilite .ch,.codehilite .cm,.codehilite .cpf,.codehilite .cs{color:#3d7b7b;font-style:italic}.codehilite .err{border:1px solid red}.codehilite .k,.codehilite .kc,.codehilite .kd,.codehilite .kn,.codehilite .kr,.codehilite .nt{color:green;font-weight:700}.codehilite .il,.codehilite .m,.codehilite .mb,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.codehilite .o{color:#666}.codehilite .cp{color:#9c6500}.codehilite .gd{color:#a00000}.codehilite .ge{font-style:italic}.codehilite .ges{font-weight:700;font-style:italic}.codehilite .gr{color:#e40000}.codehilite .gh,.codehilite .gp{color:navy;font-weight:700}.codehilite .gi{color:#008400}.codehilite .go{color:#717171}.codehilite .gs{font-weight:700}.codehilite .gu{color:purple;font-weight:700}.codehilite .gt{color:#04d}.codehilite .bp,.codehilite .kp,.codehilite .nb,.codehilite .sx{color:green}.codehilite .kt{color:#b00040}.codehilite .dl,.codehilite .s,.codehilite .s1,.codehilite .s2,.codehilite .sa,.codehilite .sb,.codehilite .sc,.codehilite .sh{color:#ba2121}.codehilite .na{color:#687822}.codehilite .nc,.codehilite .nn{color:#00f;font-weight:700}.codehilite .no{color:#800}.codehilite .nd{color:#a2f}.codehilite .ni{color:#717171;font-weight:700}.codehilite .ne{color:#cb3f38;font-weight:700}.codehilite .fm,.codehilite .nf{color:#00f}.codehilite .nl{color:#767600}.codehilite .nv,.codehilite .ss,.codehilite .vc,.codehilite .vg,.codehilite .vi,.codehilite .vm{color:#19177c}.codehilite .ow{color:#a2f;font-weight:700}.codehilite .w{color:#bbb}.codehilite .sd{color:#ba2121;font-style:italic}.codehilite .se{color:#aa5d1f;font-weight:700}.codehilite .si{color:#a45a77;font-weight:700}.codehilite .sr{color:#a45a77}
|
||||
1
templates/assets/css/brand-reveal.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.name-container{display:inline-flex;align-items:center;overflow:hidden;position:absolute;width:fit-content;left:50%;transform:translateX(-50%)}.slider{position:relative;left:0;animation:1s linear 1s forwards slide}@keyframes slide{0%{left:0}100%{left:calc(100%)}}.brand{mask-image:linear-gradient(to right,black 50%,transparent 50%);-webkit-mask-image:linear-gradient(to right,black 50%,transparent 50%);mask-position:100% 0;-webkit-mask-position:100% 0;mask-size:200%;-webkit-mask-size:200%;animation:1s linear 1s forwards reveal}@keyframes reveal{0%{mask-position:100% 0;-webkit-mask-position:100% 0}100%{mask-position:0 0;-webkit-mask-position:0 0}}.now-playing{position:fixed;bottom:0;right:0;border-top-left-radius:10px;background:#10101039;padding:1em}.hr-l{width:80%;border-width:2px;border-color:var(--bs-light);margin-top:0;opacity:.8}.hr-l-primary{border-width:3px;border-color:var(--bs-primary);margin-top:0;opacity:1}.float-right{position:absolute;right:3em}
|
||||
104
templates/assets/css/resume-print.css
Normal file
@@ -0,0 +1,104 @@
|
||||
/* print.css */
|
||||
@media print {
|
||||
|
||||
/* Page margins */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 10mm 10mm;
|
||||
}
|
||||
|
||||
/* Reset body */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10pt; /* smaller for print */
|
||||
line-height: 1.3;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
/* Container adjustments */
|
||||
.container-fluid, .resume-row, .resume-column {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-sizing: border-box;
|
||||
/* background: none !important; */
|
||||
}
|
||||
|
||||
/* Flex layout for 33/66 split */
|
||||
.resume-row {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.resume-column-left {
|
||||
flex: 0 0 33.3333% !important;
|
||||
max-width: 33.3333% !important;
|
||||
padding-left: 5mm !important;
|
||||
padding-right: 5mm !important;
|
||||
color: #fff !important;
|
||||
border: none !important;
|
||||
break-inside: avoid !important;
|
||||
page-break-inside: avoid !important;
|
||||
}
|
||||
.resume-column-left a {
|
||||
color: #fff !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.resume-column-right {
|
||||
flex: 0 0 66.6667% !important;
|
||||
max-width: 66.6667% !important;
|
||||
padding-left: 5mm !important;
|
||||
padding-right: 5mm !important;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
border: none !important;
|
||||
break-inside: avoid !important;
|
||||
page-break-inside: avoid !important;
|
||||
}
|
||||
|
||||
/* Images adjustments */
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
display: block;
|
||||
margin: 10mm auto !important;
|
||||
}
|
||||
|
||||
/* Text adjustments for print */
|
||||
h1 { font-size: 14pt; margin-bottom: 3mm; }
|
||||
h2 { font-size: 12pt; margin-bottom: 2mm; }
|
||||
h3 { font-size: 11pt; margin-bottom: 2mm; }
|
||||
h4 { font-size: 10pt; margin-bottom: 1mm; }
|
||||
h5, h6 { font-size: 9pt; margin-bottom: 1mm; }
|
||||
p, li, .r-body, .l-body { font-size: 10pt; line-height: 1.3; }
|
||||
|
||||
.title {
|
||||
font-size: 36px !important;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
.r-heading1 {
|
||||
margin-top: 4mm !important;
|
||||
}
|
||||
|
||||
/* Links as plain text */
|
||||
a {
|
||||
color: #000 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
/* Avoid page breaks inside blocks */
|
||||
.noprintbreak {
|
||||
break-inside: avoid !important;
|
||||
page-break-inside: avoid !important;
|
||||
/* margin-bottom: 5mm !important; */
|
||||
}
|
||||
.r-body {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
2
templates/assets/css/resume.min.css
vendored
@@ -1 +1 @@
|
||||
.profile-container{height:170px;width:170px;z-index:2;left:10%}.title{position:absolute;margin-left:calc(100px);width:calc(100% - 100px);padding:1em;margin-top:-225px;z-index:0}.title>*{width:100%;margin-bottom:0}img.profile{left:10px;width:150px;position:absolute;aspect-ratio:1;transform:scale(1);transition:.5s;z-index:2}img.background2{left:0;width:170px!important;margin-top:-10px;pointer-events:none;z-index:1}img.foreground{border-radius:50%;pointer-events:none;z-index:3}img.background:hover,img.backgroundsml:hover{filter:blur(5px)}.spacer{height:100px}img.profilesml{width:150px;position:absolute;left:50%;margin-left:-75px;aspect-ratio:1;padding-top:calc(var(--s)/5);transform:scale(1);transition:.5s}img.foregroundsml{border-radius:50%;pointer-events:none}img.background2sml{width:170px!important;left:calc(50% - 10px);margin-top:-10px;pointer-events:none;z-index:0}@media print{div{color:#000!important}.noprintbreak{page-break-inside:avoid}.edu-main{page-break-before:always}}
|
||||
img.profile-side{width:200px;aspect-ratio:1;z-index:2;border:6px solid #fff;margin:3em 0;border-radius:50%}.spacer{height:100px}.l-heading1,.l-heading2,.r-heading2{margin-bottom:0}.l-heading3,.r-heading3{margin-bottom:.5em}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{text-transform:none}.side-column{margin-top:2em}.noprintbreak{margin-bottom:1.5em}.resume-column-left{background:var(--bs-primary);padding-left:3em;padding-right:3em;max-width:320px}.resume-column-right{padding-right:3em;padding-left:3em;background:var(--bs-light);color:var(--bs-black)}.row-fill div{padding:0}.r-heading1{font-size:28px;margin-bottom:0;color:var(--bs-primary)}.title-hr{width:15%;color:var(--bs-primary);border-width:5px;border-color:var(--bs-primary);opacity:1}.l-body{margin-left:1em;line-height:initial}.r-body{line-height:initial}.l-summary{margin-top:3em}::selection{color:#fff;background-color:#0c4279}body{max-width:1400px;margin:0 auto}
|
||||
2
templates/assets/css/styles.min.css
vendored
@@ -1 +1 @@
|
||||
.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}@media (max-width:500px){img.profile{width:200px;margin-left:-100px}.profile-container{height:200px;margin-top:2em!important}}.social-div{display:flex;justify-content:center;align-items:center}.social-list-sml{display:flex;list-style:none;gap:1rem}.social-list{display:flex;list-style:none;gap:2.5rem}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link-sml a{text-decoration:none;width:3.5rem;height:3.5rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before,.social-link-sml a::before{content:"";position:absolute;width:100%;height:100%;background:var(--bg-color);z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link-sml:hover a::before,.social-link:hover a::before{scale:1 1}.icon-sml{font-size:1.5rem;color:#011827;transition:.5s;z-index:2}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}.social-link,.social-link-sml{--bg-color:#000}.social-link-sml.discord,.social-link.discord{--bg-color:#5865f2}.social-link-sml.mastodon,.social-link.mastodon{--bg-color:#6364ff}.social-link-sml.youtube,.social-link.youtube{--bg-color:#ff0000}.social-link-sml.signal,.social-link.signal{--bg-color:#365eb6}
|
||||
:root,[data-bs-theme=light]{--bs-primary:#6E0E9C;--bs-primary-rgb:110,14,156;--bs-primary-text-emphasis:#2C063E;--bs-primary-bg-subtle:#E2CFEB;--bs-primary-border-subtle:#C59FD7;--bs-link-color:#6E0E9C;--bs-link-color-rgb:110,14,156;--bs-link-hover-color:#a41685;--bs-link-hover-color-rgb:164,22,133}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5E0C85;--bs-btn-hover-border-color:#580B7D;--bs-btn-focus-shadow-rgb:233,219,240;--bs-btn-active-color:#fff;--bs-btn-active-bg:#580B7D;--bs-btn-active-border-color:#530B75;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6E0E9C;--bs-btn-disabled-border-color:#6E0E9C}.btn-outline-primary{--bs-btn-color:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-focus-shadow-rgb:110,14,156;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6E0E9C;--bs-btn-hover-border-color:#6E0E9C;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6E0E9C;--bs-btn-active-border-color:#6E0E9C;--bs-btn-disabled-color:#6E0E9C;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6E0E9C}
|
||||
1
templates/assets/css/tools.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.card:hover{transform:translateY(-5px);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);transition:transform .2s,box-shadow .2s}.btn:hover{transform:scale(1.05);transition:transform .2s}
|
||||
BIN
templates/assets/img/bg/BlueMountains.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
templates/assets/img/bg/bg_sunset.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
templates/assets/img/bg/sunset.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
templates/assets/img/external/spotify.png
vendored
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
templates/assets/img/external/stWDBRN.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
templates/assets/img/nathanwoodburn.jpeg
Normal file
|
After Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 12 KiB |
BIN
templates/assets/img/profile.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
templates/assets/js/403.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
const trigger="s",secret="/supersecretpath",home="/";var isSecret=!1;function error_check(){return function(){isSecret?(alert("You found the secret path"),window.location=secret):(alert("This page is not readable!"))}}function type(e,t){var n=document.getElementsByTagName("code")[e].innerHTML.toString(),o=0;document.getElementsByTagName("code")[e].innerHTML="",setTimeout((function(){var t=setInterval((function(){o++,document.getElementsByTagName("code")[e].innerHTML=n.slice(0,o)+"|",o==n.length&&(clearInterval(t),document.getElementsByTagName("code")[e].innerHTML=n)}),10)}),t)}setTimeout(error_check(),5e3),document.addEventListener("keydown",(function(e){"s"==e.key&&(isSecret=!0)})),type(0,0),type(1,600),type(2,1300);
|
||||
@@ -1,446 +0,0 @@
|
||||
;(function() {
|
||||
"use strict";
|
||||
|
||||
// General
|
||||
var canvas,
|
||||
screen,
|
||||
gameSize,
|
||||
game;
|
||||
|
||||
// Assets
|
||||
var invaderCanvas,
|
||||
invaderMultiplier,
|
||||
invaderSize = 20,
|
||||
initialOffsetInvader,
|
||||
invaderAttackRate,
|
||||
invaderSpeed,
|
||||
invaderSpawnDelay = 250;
|
||||
|
||||
// Counter
|
||||
var i = 0,
|
||||
kills = 0,
|
||||
spawnDelayCounter = invaderSpawnDelay;
|
||||
|
||||
var invaderDownTimer;
|
||||
|
||||
// Text
|
||||
var blocks = [
|
||||
[3, 4, 8, 9, 10, 15, 16],
|
||||
[2, 4, 7, 11, 14, 16],
|
||||
[1, 4, 7, 11, 13, 16],
|
||||
[1, 2, 3, 4, 5, 7, 11, 13, 14, 15, 16, 17],
|
||||
[4, 7, 11, 16],
|
||||
[4, 8, 9, 10, 16]
|
||||
];
|
||||
|
||||
// Game Controller
|
||||
// ---------------
|
||||
var Game = function() {
|
||||
|
||||
this.level = -1;
|
||||
this.lost = false;
|
||||
|
||||
this.player = new Player();
|
||||
this.invaders = [];
|
||||
this.invaderShots = [];
|
||||
|
||||
if (invaderDownTimer === undefined) {
|
||||
invaderDownTimer = setInterval(function() {
|
||||
for (i = 0; i < game.invaders.length; i++) game.invaders[i].move();
|
||||
}, 1000 - (this.level * 1.8));
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
Game.prototype = {
|
||||
update: function() {
|
||||
|
||||
// Next level
|
||||
if (game.invaders.length === 0) {
|
||||
|
||||
spawnDelayCounter += 1;
|
||||
if (spawnDelayCounter < invaderSpawnDelay) return;
|
||||
|
||||
this.level += 1;
|
||||
|
||||
invaderAttackRate -= 0.002;
|
||||
invaderSpeed += 10;
|
||||
|
||||
game.invaders = createInvaders();
|
||||
|
||||
spawnDelayCounter = 0;
|
||||
}
|
||||
|
||||
if (!this.lost) {
|
||||
|
||||
// Collision
|
||||
game.player.projectile.forEach(function(projectile) {
|
||||
game.invaders.forEach(function(invader) {
|
||||
if (collides(projectile, invader)) {
|
||||
invader.destroy();
|
||||
projectile.active = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.invaderShots.forEach(function(invaderShots) {
|
||||
if (collides(invaderShots, game.player)) {
|
||||
game.player.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
for (i = 0; i < game.invaders.length; i++) game.invaders[i].update();
|
||||
|
||||
}
|
||||
|
||||
// Don't stop player & projectiles.. they look nice
|
||||
game.player.update();
|
||||
for (i = 0; i < game.invaderShots.length; i++) game.invaderShots[i].update();
|
||||
|
||||
this.invaders = game.invaders.filter(function(invader) {
|
||||
return invader.active;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
|
||||
if (this.lost) {
|
||||
screen.fillStyle = "rgba(0, 0, 0, 0.03)";
|
||||
screen.fillRect(0, 0, gameSize.width, gameSize.height);
|
||||
|
||||
screen.font = "55px Lucida Console";
|
||||
screen.textAlign = "center";
|
||||
screen.fillStyle = "white";
|
||||
screen.fillText("You lost", gameSize.width / 2, gameSize.height / 2);
|
||||
screen.font = "20px Lucida Console";
|
||||
screen.fillText("Points: " + kills, gameSize.width / 2, gameSize.height / 2 + 30);
|
||||
|
||||
} else {
|
||||
screen.clearRect(0, 0, gameSize.width, gameSize.height);
|
||||
|
||||
screen.font = "10px Lucida Console";
|
||||
screen.textAlign = "right";
|
||||
screen.fillText("Points: " + kills, gameSize.width, gameSize.height - 12);
|
||||
}
|
||||
|
||||
screen.beginPath();
|
||||
|
||||
var i;
|
||||
this.player.draw();
|
||||
if (!this.lost)
|
||||
for (i = 0; i < this.invaders.length; i++) this.invaders[i].draw();
|
||||
for (i = 0; i < this.invaderShots.length; i++) this.invaderShots[i].draw();
|
||||
|
||||
screen.fill();
|
||||
|
||||
},
|
||||
|
||||
invadersBelow: function(invader) {
|
||||
return this.invaders.filter(function(b) {
|
||||
return Math.abs(invader.coordinates.x - b.coordinates.x) === 0 &&
|
||||
b.coordinates.y > invader.coordinates.y;
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Invaders
|
||||
// --------
|
||||
var Invader = function(coordinates) {
|
||||
this.active = true;
|
||||
this.coordinates = coordinates;
|
||||
this.size = {
|
||||
width: invaderSize,
|
||||
height: invaderSize
|
||||
};
|
||||
|
||||
this.patrolX = 0;
|
||||
this.speedX = invaderSpeed;
|
||||
|
||||
};
|
||||
|
||||
Invader.prototype = {
|
||||
update: function() {
|
||||
|
||||
if (Math.random() > invaderAttackRate && !game.invadersBelow(this)) {
|
||||
var projectile = new Projectile({
|
||||
x: this.coordinates.x + this.size.width / 2,
|
||||
y: this.coordinates.y + this.size.height - 5
|
||||
}, {
|
||||
x: 0,
|
||||
y: 2
|
||||
});
|
||||
game.invaderShots.push(projectile);
|
||||
}
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) screen.drawImage(invaderCanvas, this.coordinates.x, this.coordinates.y);
|
||||
|
||||
},
|
||||
move: function() {
|
||||
if (this.patrolX < 0 || this.patrolX > 100) {
|
||||
this.speedX = -this.speedX;
|
||||
this.patrolX += this.speedX;
|
||||
this.coordinates.y += this.size.height;
|
||||
|
||||
if (this.coordinates.y + this.size.height * 2 > gameSize.height) game.lost = true;
|
||||
|
||||
} else {
|
||||
this.coordinates.x += this.speedX;
|
||||
this.patrolX += this.speedX;
|
||||
}
|
||||
|
||||
},
|
||||
destroy: function() {
|
||||
this.active = false;
|
||||
kills += 1;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Player
|
||||
// ------
|
||||
var Player = function() {
|
||||
this.active = true;
|
||||
this.size = {
|
||||
width: 16,
|
||||
height: 8
|
||||
};
|
||||
this.shooterHeat = -3;
|
||||
this.coordinates = {
|
||||
x: gameSize.width / 2 - (this.size.width / 2) | 0,
|
||||
y: gameSize.height - this.size.height * 2
|
||||
};
|
||||
|
||||
this.projectile = [];
|
||||
this.keyboarder = new KeyController();
|
||||
};
|
||||
|
||||
Player.prototype = {
|
||||
update: function() {
|
||||
|
||||
for (var i = 0; i < this.projectile.length; i++) this.projectile[i].update();
|
||||
|
||||
this.projectile = this.projectile.filter(function(projectile) {
|
||||
return projectile.active;
|
||||
});
|
||||
|
||||
if (!this.active) return;
|
||||
|
||||
if (this.keyboarder.isDown(this.keyboarder.KEYS.LEFT) && this.coordinates.x > 0) this.coordinates.x -= 2;
|
||||
else if (this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT) && this.coordinates.x < gameSize.width - this.size.width) this.coordinates.x += 2;
|
||||
|
||||
if (this.keyboarder.isDown(this.keyboarder.KEYS.Space)) {
|
||||
this.shooterHeat += 1;
|
||||
if (this.shooterHeat < 0) {
|
||||
var projectile = new Projectile({
|
||||
x: this.coordinates.x + this.size.width / 2 - 1,
|
||||
y: this.coordinates.y - 1
|
||||
}, {
|
||||
x: 0,
|
||||
y: -7
|
||||
});
|
||||
this.projectile.push(projectile);
|
||||
} else if (this.shooterHeat > 12) this.shooterHeat = -3;
|
||||
} else {
|
||||
this.shooterHeat = -3;
|
||||
}
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) {
|
||||
screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height);
|
||||
screen.rect(this.coordinates.x - 2, this.coordinates.y + 2, 20, 6);
|
||||
screen.rect(this.coordinates.x + 6, this.coordinates.y - 4, 4, 4);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.projectile.length; i++) this.projectile[i].draw();
|
||||
|
||||
},
|
||||
destroy: function() {
|
||||
this.active = false;
|
||||
game.lost = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Projectile
|
||||
// ------
|
||||
var Projectile = function(coordinates, velocity) {
|
||||
this.active = true;
|
||||
this.coordinates = coordinates;
|
||||
this.size = {
|
||||
width: 3,
|
||||
height: 3
|
||||
};
|
||||
this.velocity = velocity;
|
||||
};
|
||||
|
||||
Projectile.prototype = {
|
||||
update: function() {
|
||||
this.coordinates.x += this.velocity.x;
|
||||
this.coordinates.y += this.velocity.y;
|
||||
|
||||
if (this.coordinates.y > gameSize.height || this.coordinates.y < 0) this.active = false;
|
||||
|
||||
},
|
||||
draw: function() {
|
||||
if (this.active) screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Keyboard input tracking
|
||||
// -----------------------
|
||||
var KeyController = function() {
|
||||
this.KEYS = {
|
||||
LEFT: 37,
|
||||
RIGHT: 39,
|
||||
Space: 32
|
||||
};
|
||||
var keyCode = [37, 39, 32];
|
||||
var keyState = {};
|
||||
|
||||
var counter;
|
||||
window.addEventListener('keydown', function(e) {
|
||||
for (counter = 0; counter < keyCode.length; counter++)
|
||||
if (keyCode[counter] == e.keyCode) {
|
||||
keyState[e.keyCode] = true;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', function(e) {
|
||||
for (counter = 0; counter < keyCode.length; counter++)
|
||||
if (keyCode[counter] == e.keyCode) {
|
||||
keyState[e.keyCode] = false;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.isDown = function(keyCode) {
|
||||
return keyState[keyCode] === true;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// Other functions
|
||||
// ---------------
|
||||
function collides(a, b) {
|
||||
return a.coordinates.x < b.coordinates.x + b.size.width &&
|
||||
a.coordinates.x + a.size.width > b.coordinates.x &&
|
||||
a.coordinates.y < b.coordinates.y + b.size.height &&
|
||||
a.coordinates.y + a.size.height > b.coordinates.y;
|
||||
}
|
||||
|
||||
function getPixelRow(rowRaw) {
|
||||
var textRow = [],
|
||||
placer = 0,
|
||||
row = Math.floor(rowRaw / invaderMultiplier);
|
||||
if (row >= blocks.length) return [];
|
||||
for (var i = 0; i < blocks[row].length; i++) {
|
||||
var tmpContent = blocks[row][i] * invaderMultiplier;
|
||||
for (var j = 0; j < invaderMultiplier; j++) textRow[placer + j] = tmpContent + j;
|
||||
placer += invaderMultiplier;
|
||||
}
|
||||
return textRow;
|
||||
}
|
||||
|
||||
// Write Text
|
||||
// -----------
|
||||
function createInvaders() {
|
||||
var invaders = [];
|
||||
|
||||
var i = blocks.length * invaderMultiplier;
|
||||
while (i--) {
|
||||
var j = getPixelRow(i);
|
||||
for (var k = 0; k < j.length; k++) {
|
||||
invaders.push(new Invader({
|
||||
x: j[k] * invaderSize,
|
||||
y: i * invaderSize
|
||||
}));
|
||||
}
|
||||
}
|
||||
return invaders;
|
||||
}
|
||||
|
||||
// Start game
|
||||
// ----------
|
||||
window.addEventListener('load', function() {
|
||||
|
||||
var invaderAsset = new Image;
|
||||
invaderAsset.onload = function() {
|
||||
|
||||
invaderCanvas = document.createElement('canvas');
|
||||
invaderCanvas.width = invaderSize;
|
||||
invaderCanvas.height = invaderSize;
|
||||
invaderCanvas.getContext("2d").drawImage(invaderAsset, 0, 0);
|
||||
|
||||
// Game Creation
|
||||
canvas = document.getElementById("space-invaders");
|
||||
screen = canvas.getContext('2d');
|
||||
|
||||
initGameStart();
|
||||
loop();
|
||||
|
||||
};
|
||||
invaderAsset.src = "/assets/img/invader.gif";
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
initGameStart();
|
||||
});
|
||||
document.getElementById('restart').addEventListener('click', function() {
|
||||
initGameStart();
|
||||
});
|
||||
|
||||
function initGameStart() {
|
||||
if (window.innerWidth > 1200) {
|
||||
screen.canvas.width = 1200;
|
||||
screen.canvas.height = 500;
|
||||
gameSize = {
|
||||
width: 1200,
|
||||
height: 500
|
||||
};
|
||||
invaderMultiplier = 3;
|
||||
initialOffsetInvader = 420;
|
||||
} else if (window.innerWidth > 800) {
|
||||
screen.canvas.width = 900;
|
||||
screen.canvas.height = 600;
|
||||
gameSize = {
|
||||
width: 900,
|
||||
height: 600
|
||||
};
|
||||
invaderMultiplier = 2;
|
||||
initialOffsetInvader = 280;
|
||||
} else {
|
||||
screen.canvas.width = 600;
|
||||
screen.canvas.height = 300;
|
||||
gameSize = {
|
||||
width: 600,
|
||||
height: 300
|
||||
};
|
||||
invaderMultiplier = 1;
|
||||
initialOffsetInvader = 140;
|
||||
}
|
||||
|
||||
kills = 0;
|
||||
invaderAttackRate = 0.999;
|
||||
invaderSpeed = 20;
|
||||
spawnDelayCounter = invaderSpawnDelay;
|
||||
|
||||
game = new Game();
|
||||
}
|
||||
|
||||
function loop() {
|
||||
game.update();
|
||||
game.draw();
|
||||
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
})();
|
||||
1
templates/assets/js/grayscale.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e=document.querySelector("#mainNav");if(e){var o=e.querySelector(".navbar-collapse");if(o){var n=new bootstrap.Collapse(o,{toggle:!1}),t=o.querySelectorAll("a");for(var a of t)a.addEventListener("click",(function(e){n.hide()}))}var r=function(){(void 0!==window.pageYOffset?window.pageYOffset:(document.documentElement||document.body.parentNode||document.body).scrollTop)>100?e.classList.add("navbar-shrink"):e.classList.remove("navbar-shrink")};r(),document.addEventListener("scroll",r)}}();
|
||||
2
templates/assets/js/hacker.min.js
vendored
@@ -1 +1 @@
|
||||
const letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ/.?!@#$%^&*()_+";let interval=null,interval2=null,interval3=null;window.onload=t=>{target=document.querySelector(".nathanwoodburn");let e=0,n="NATHAN.WOODBURN/";clearInterval(interval),interval=setInterval((()=>{target.innerText=target.innerText.split("").map(((t,r)=>r<e?n[r]:letters[Math.floor(41*Math.random())])).join(""),e>=n.length&&clearInterval(interval),e+=1/3}),30)},document.querySelector(".copyright").onmouseover=t=>{let e=0,n="Copyright © Nathan.Woodburn/ 2024";console.log(n),clearInterval(interval2),interval2=setInterval((()=>{t.target.innerText=t.target.innerText.split("").map(((t,r)=>r<e?n[r]:letters[Math.floor(41*Math.random())])).join(""),e>=n.length&&clearInterval(interval2),e+=1/3}),10)};
|
||||
const letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ/.?!@#$%^&*()_+";let interval=null,interval2=null,interval3=null;window.onload=t=>{if(target=document.querySelector(".nathanwoodburn"),target){let t=0,e="NATHAN.WOODBURN/";clearInterval(interval),interval=setInterval((()=>{target.innerText=target.innerText.split("").map(((r,n)=>n<t?e[n]:letters[Math.floor(41*Math.random())])).join(""),t>=e.length&&clearInterval(interval),t+=1/3}),30)}},document.querySelector(".copyright").onmouseover=t=>{let e=0,r="Copyright © Nathan.Woodburn/ 2025";console.log(r),clearInterval(interval2),interval2=setInterval((()=>{t.target.innerText=t.target.innerText.split("").map(((t,n)=>n<e?r[n]:letters[Math.floor(41*Math.random())])).join(""),e>=r.length&&clearInterval(interval2),e+=1/3}),10)};
|
||||
2
templates/assets/js/loading.min.js
vendored
@@ -1 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(function(){const n=document.getElementById("loading-screen"),e=[{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~</span>]',message:"cd Git"},{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git</span>]',message:"cd Nathanwoodburn.github.io"},{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git/Nathanwoodburn.github.io</span>]',message:"python3 main.py"}],t=["Starting server with 1 workers and 2 threads","+0000] [1] [INFO] Starting gunicorn 22.0.0","+0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)","+0000] [1] [INFO] Using worker: gthread","+0000] [8] [INFO] Booting worker with pid: 8"];let a=0;function s(e,t){const a=function(){const n=new Date;return`${n.getUTCFullYear()}-${String(n.getUTCMonth()+1).padStart(2,"0")}-${String(n.getUTCDate()).padStart(2,"0")} ${String(n.getUTCHours()).padStart(2,"0")}:${String(n.getUTCMinutes()).padStart(2,"0")}:${String(n.getUTCSeconds()).padStart(2,"0")}`}();for(let t=0;t<e.length;t++){const s=document.createElement("div");s.classList.add("loading-line"),s.innerHTML=0!==t?"["+a+e[t]:e[t],n.appendChild(s)}t()}function i(){"/"===window.location.pathname?window.location.reload():window.location.href="/"}window.addEventListener("keypress",i);window.innerWidth<768&&(console.log("Screen width is less than 768px, allowing click to redirect"),window.addEventListener("click",i)),function r(){a<e.length?function(e,t){const a=document.createElement("div");a.classList.add("loading-pre"),a.innerHTML=e.pre,n.appendChild(a);const s=document.createElement("div");s.classList.add("loading-line"),s.innerHTML='└─<span class="blue">$</span> <span class="cursor"></span>',n.appendChild(s);let i=0;const r=setInterval((()=>{s.removeChild(s.querySelector(".cursor")),s.innerHTML+=e.message[i]+'<span class="cursor"></span>',i++,i===e.message.length&&(s.removeChild(s.querySelector(".cursor")),clearInterval(r),t())}),50)}(e[a],(()=>{a++,setTimeout(r,200)})):s(t,(()=>{setTimeout(i,200)}))}()}));
|
||||
document.addEventListener("DOMContentLoaded",(function(){const s=document.getElementById("loading-screen"),e=[{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~</span>]',message:"cd Git"},{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git</span>]',message:"cd Nathanwoodburn.github.io"},{pre:'┌──(<span class="blue">nathan@NWTux</span>)-[<span class="white">~/Git/Nathanwoodburn.github.io</span>]',message:"python3 main.py"}],t=["Starting server with 1 workers and 2 threads","+0000] [1] [INFO] Starting gunicorn 22.0.0","+0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)","+0000] [1] [INFO] Using worker: gthread","+0000] [8] [INFO] Booting worker with pid: 8"];let n=0,a=!1,i=!1;function r(){i&&setTimeout(l,200)}function o(e,t){const n=function(){const s=new Date;return`${s.getUTCFullYear()}-${String(s.getUTCMonth()+1).padStart(2,"0")}-${String(s.getUTCDate()).padStart(2,"0")} ${String(s.getUTCHours()).padStart(2,"0")}:${String(s.getUTCMinutes()).padStart(2,"0")}:${String(s.getUTCSeconds()).padStart(2,"0")}`}();for(let t=0;t<e.length;t++){const a=document.createElement("div");a.classList.add("loading-line"),a.innerHTML=0!==t?"["+n+"] "+e[t]:e[t],s.appendChild(a)}i=!0,t()}function l(){"/"===window.location.pathname?window.location.reload():window.location.href="/"}window.addEventListener("keypress",l),window.innerWidth<768&&(console.log("Screen width is less than 768px, allowing click to redirect"),window.addEventListener("click",l)),setTimeout((function(){const s=[{url:"/assets/fonts/fontawesome-all.min.css",type:"style"},{url:"/assets/fonts/font-awesome.min.css",type:"style"},{url:"/assets/fonts/ionicons.min.css",type:"style"},{url:"/assets/fonts/fontawesome5-overrides.min.css",type:"style"},{url:"/assets/css/index.min.css",type:"style"},{url:"/assets/css/swiper.min.css",type:"style"},{url:"/assets/css/animate.min.min.css",type:"style"},{url:"/assets/css/fixes.min.css",type:"style"},{url:"/assets/css/Footer-Dark-icons.min.css",type:"style"},{url:"/assets/css/GridSystem-1.min.css",type:"style"},{url:"/assets/js/hacker.min.js",type:"script"},{url:"/assets/js/downtime.min.js",type:"script"},{url:"/assets/js/pfp.min.js",type:"script"},{url:"/assets/js/sites.min.js",type:"script"},{url:"/assets/img/pfront.webp",type:"image"},{url:"/assets/img/profile.jpg",type:"image"},{url:"/assets/img/tilt.svg",type:"image"},{url:"/assets/img/bg/BlueMountains.jpg",type:"image"},{url:"/assets/img/wavesblack.svg",type:"image"}];let e=0;const t=s.length;function n(){e++,e===t&&(a=!0,r())}s.forEach((s=>{const e=document.createElement("link");e.rel="preload",e.as=s.type,e.href=s.url,"font"===s.type&&(e.crossOrigin="anonymous"),e.onload=n,e.onerror=n,document.head.appendChild(e)}))}),100),function a(){n<e.length?function(e,t){const n=document.createElement("div");n.classList.add("loading-pre"),n.innerHTML=e.pre,s.appendChild(n);const a=document.createElement("div");a.classList.add("loading-line"),a.innerHTML='└─<span class="blue">$</span> <span class="cursor"></span>',s.appendChild(a);let i=0;const r=setInterval((()=>{a.removeChild(a.querySelector(".cursor")),a.innerHTML+=e.message[i]+'<span class="cursor"></span>',i++,i===e.message.length&&(a.removeChild(a.querySelector(".cursor")),clearInterval(r),t())}),50)}(e[n],(()=>{n++,setTimeout(a,200)})):o(t,(()=>{r()}))}()}));
|
||||
2
templates/assets/js/script.min.js
vendored
@@ -1 +1 @@
|
||||
window.innerWidth<768&&[].slice.call(document.querySelectorAll("[data-bss-disabled-mobile]")).forEach((function(e){e.classList.remove("animated"),e.removeAttribute("data-bss-hover-animate"),e.removeAttribute("data-aos"),e.removeAttribute("data-bss-parallax-bg"),e.removeAttribute("data-bss-scroll-zoom")})),document.addEventListener("DOMContentLoaded",(function(){[].slice.call(document.querySelectorAll("[data-bss-hover-animate]")).forEach((function(e){e.addEventListener("mouseenter",(function(e){e.target.classList.add("animated",e.target.dataset.bssHoverAnimate)})),e.addEventListener("mouseleave",(function(e){e.target.classList.remove("animated",e.target.dataset.bssHoverAnimate)}))})),[].slice.call(document.querySelectorAll("[data-bss-tooltip]")).map((function(e){return new bootstrap.Tooltip(e)}))}),!1),function(){"use strict";var e=document.querySelector("#mainNav");if(e){var t=e.querySelector(".navbar-collapse");if(t){var a=new bootstrap.Collapse(t,{toggle:!1}),o=t.querySelectorAll("a");for(var n of o)n.addEventListener("click",(function(e){a.hide()}))}var r=function(){(void 0!==window.pageYOffset?window.pageYOffset:(document.documentElement||document.body.parentNode||document.body).scrollTop)>100?e.classList.add("navbar-shrink"):e.classList.remove("navbar-shrink")};r(),document.addEventListener("scroll",r)}}();
|
||||
window.innerWidth<768&&[].slice.call(document.querySelectorAll("[data-bss-disabled-mobile]")).forEach((function(e){e.classList.remove("animated"),e.removeAttribute("data-bss-hover-animate"),e.removeAttribute("data-aos"),e.removeAttribute("data-bss-parallax-bg"),e.removeAttribute("data-bss-scroll-zoom")})),document.addEventListener("DOMContentLoaded",(function(){[].slice.call(document.querySelectorAll("[data-bss-hover-animate]")).forEach((function(e){e.addEventListener("mouseenter",(function(e){e.target.classList.add("animated",e.target.dataset.bssHoverAnimate)})),e.addEventListener("mouseleave",(function(e){e.target.classList.remove("animated",e.target.dataset.bssHoverAnimate)}))})),[].slice.call(document.querySelectorAll("[data-bss-tooltip]")).map((function(e){return new bootstrap.Tooltip(e)}))}),!1);
|
||||
16
templates/assets/stwdbrn_metadata.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Woodburn Vault",
|
||||
"symbol": "stWDBRN",
|
||||
"image": "https://nathan.woodburn.au/stWDBRN.png",
|
||||
"description": "Woodburn Vault Token",
|
||||
"extensions": {
|
||||
"website": "https://nathan.woodburn.au"
|
||||
},
|
||||
"tags": [
|
||||
"Woodburn"
|
||||
],
|
||||
"creator": {
|
||||
"name": "Nathan.Woodburn/",
|
||||
"site": "https://nathan.woodburn.au"
|
||||
}
|
||||
}
|
||||
155
templates/blog/blog.html
Normal file
@@ -0,0 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="background: black;height: auto;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Blog | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/blog/blog">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/blog/blog">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<meta property="og:title" content="Blog | Nathan.Woodburn/">
|
||||
<meta name="description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<meta name="twitter:title" content="Blog | Nathan.Woodburn/">
|
||||
<meta name="twitter:description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>{{handshake_scripts | safe}}
|
||||
<div style="height: 10em;"></div>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;"><img class="profile foreground" src="/assets/img/pfront.webp"></div>
|
||||
<h1 class="nathanwoodburn" style="margin-bottom: 0px;">Nathan.Woodburn/</h1>
|
||||
<h3 style="margin-bottom: 0px;">BlogS</h3>
|
||||
<h6>Check out my various blog posts below</h6>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;">
|
||||
<div style="max-width: 700px;margin: auto;">
|
||||
<h1 style="margin-bottom: 0px;">{{blogs | safe}}</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section class="text-center content-section" id="contact" style="padding-top: 0px;padding-bottom: 3em;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 d-none d-print-block d-sm-block d-md-block d-lg-block d-xl-block d-xxl-block mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon"></i></a></li>
|
||||
<li class="social-link discord"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link mastodon"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link youtube"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link signal"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 d-block d-print-none d-sm-none d-md-none d-lg-none d-xl-none d-xxl-none mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="social-link-sml"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon-sml">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon-sml">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon-sml"></i></a></li>
|
||||
<li class="discord social-link-sml"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon-sml">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="mastodon social-link-sml"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon-sml">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="youtube social-link-sml"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon-sml">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="signal social-link-sml"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon-sml">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
153
templates/blog/template.html
Normal file
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="background: black;height: auto;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>{{title}} | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/blog/template">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/blog/template">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:title" content="{{title}} | Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<meta name="description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<meta name="twitter:description" content="G'day,
|
||||
Find something interesting to read. Or maybe check one of my tutorials">
|
||||
<meta name="twitter:title" content="{{title}} | Nathan.Woodburn/">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/blog.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>{{handshake_scripts | safe}}
|
||||
<div style="height: 10em;"></div>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;"><img class="profile foreground" src="/assets/img/pfront.webp"></div>
|
||||
<h1 class="nathanwoodburn" style="margin-bottom: 0px;">Nathan.Woodburn/</h1>
|
||||
<h3 style="margin-bottom: 0px;margin-top: 25px;">{{title}}</h3>
|
||||
<section style="margin-bottom: 50px;max-width: 95%;margin-right: auto;margin-left: auto;margin-top: 50px;">
|
||||
<div style="max-width: 700px;margin: auto;">{{content | safe}}</div>
|
||||
</section>
|
||||
<section class="text-center content-section" id="contact" style="padding-top: 0px;padding-bottom: 3em;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 d-none d-print-block d-sm-block d-md-block d-lg-block d-xl-block d-xxl-block mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon"></i></a></li>
|
||||
<li class="social-link discord"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list">
|
||||
<li class="social-link mastodon"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link youtube"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link signal"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 d-block d-print-none d-sm-none d-md-none d-lg-none d-xl-none d-xxl-none mx-auto">
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="social-link-sml"><a href="https://twitter.com/woodburn_nathan" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter-x icon-sml">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="https://github.com/Nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-github icon-sml">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||
</svg></a></li>
|
||||
<li class="social-link-sml"><a href="mailto:about@nathan.woodburn.au" target="_blank"><i class="icon ion-email icon-sml"></i></a></li>
|
||||
<li class="discord social-link-sml"><a href="https://l.woodburn.au/discord" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord icon-sml">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="social-div">
|
||||
<ul class="list-unstyled social-list-sml">
|
||||
<li class="mastodon social-link-sml"><a href="https://mastodon.woodburn.au/@nathanwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-mastodon icon-sml">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"></path>
|
||||
</svg></a></li>
|
||||
<li class="youtube social-link-sml"><a href="https://www.youtube.com/@nathanjwoodburn" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-youtube icon-sml">
|
||||
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||
</svg></a></li>
|
||||
<li class="signal social-link-sml"><a href="/signalQR" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-signal icon-sml">
|
||||
<path d="m6.08.234.179.727a7.264 7.264 0 0 0-2.01.832l-.383-.643A7.9 7.9 0 0 1 6.079.234zm3.84 0L9.742.96a7.265 7.265 0 0 1 2.01.832l.388-.643A7.957 7.957 0 0 0 9.92.234zm-8.77 3.63a7.944 7.944 0 0 0-.916 2.215l.727.18a7.264 7.264 0 0 1 .832-2.01l-.643-.386zM.75 8a7.3 7.3 0 0 1 .081-1.086L.091 6.8a8 8 0 0 0 0 2.398l.74-.112A7.262 7.262 0 0 1 .75 8m11.384 6.848-.384-.64a7.23 7.23 0 0 1-2.007.831l.18.728a7.965 7.965 0 0 0 2.211-.919zM15.251 8c0 .364-.028.727-.082 1.086l.74.112a7.966 7.966 0 0 0 0-2.398l-.74.114c.054.36.082.722.082 1.086m.516 1.918-.728-.18a7.252 7.252 0 0 1-.832 2.012l.643.387a7.933 7.933 0 0 0 .917-2.219zm-6.68 5.25c-.72.11-1.453.11-2.173 0l-.112.742a7.99 7.99 0 0 0 2.396 0l-.112-.741zm4.75-2.868a7.229 7.229 0 0 1-1.537 1.534l.446.605a8.07 8.07 0 0 0 1.695-1.689l-.604-.45zM12.3 2.163c.587.432 1.105.95 1.537 1.537l.604-.45a8.06 8.06 0 0 0-1.69-1.691l-.45.604zM2.163 3.7A7.242 7.242 0 0 1 3.7 2.163l-.45-.604a8.06 8.06 0 0 0-1.691 1.69l.604.45zm12.688.163-.644.387c.377.623.658 1.3.832 2.007l.728-.18a7.931 7.931 0 0 0-.916-2.214M6.913.831a7.254 7.254 0 0 1 2.172 0l.112-.74a7.985 7.985 0 0 0-2.396 0l.112.74zM2.547 14.64 1 15l.36-1.549-.729-.17-.361 1.548a.75.75 0 0 0 .9.902l1.548-.357-.17-.734zM.786 12.612l.732.168.25-1.073A7.187 7.187 0 0 1 .96 9.74l-.727.18a8 8 0 0 0 .736 1.902l-.184.79zm3.5 1.623-1.073.25.17.731.79-.184c.6.327 1.239.574 1.902.737l.18-.728a7.197 7.197 0 0 1-1.962-.811l-.007.005zM8 1.5a6.502 6.502 0 0 0-6.498 6.502 6.516 6.516 0 0 0 .998 3.455l-.625 2.668L4.54 13.5a6.502 6.502 0 0 0 6.93-11A6.516 6.516 0 0 0 8 1.5"></path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
14
templates/contact.ascii
Normal file
@@ -0,0 +1,14 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m CONTACT ME [0m
|
||||
[1;36m────────────[0m
|
||||
|
||||
Here are my socials — I’m most active on Discord 💬
|
||||
|
||||
- Twitter: https://twitter.com/woodburn_nathan
|
||||
- GitHub: https://github.com/Nathanwoodburn
|
||||
- Email: mailto:about@nathan.woodburn.au
|
||||
- Discord: https://l.woodburn.au/discord
|
||||
- Mastodon: https://mastodon.woodburn.au/@nathanwoodburn
|
||||
- YouTube: https://www.youtube.com/@nathanjwoodburn
|
||||
|
||||
25
templates/donate.ascii
Normal file
@@ -0,0 +1,25 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
If you’d like to support my work 💙
|
||||
|
||||
- PayPal: https://paypal.me/nathanwoodburn
|
||||
- GitHub: https://github.com/sponsors/Nathanwoodburn
|
||||
- Stripe: https://donate.stripe.com/8wM6pv0VD08Xe408ww
|
||||
|
||||
[1mHNS: nathan.woodburn[0m
|
||||
[1m{{ HNS }}[0m
|
||||
|
||||
[1mBTC: thinbadger6@primal.net[0m
|
||||
[1m{{ BTC }}[0m
|
||||
|
||||
[1mSOL: woodburn.sol[0m
|
||||
[1m{{ SOL }}[0m
|
||||
|
||||
[1mETH: woodburn.au[0m
|
||||
[1m{{ ETH }}[0m
|
||||
|
||||
More donation options → [/donate/more]
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/donate">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/donate">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,23 +29,32 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome5-overrides.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77">{{handshake_scripts | safe}}
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -138,7 +148,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2024</p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,6 +200,7 @@
|
||||
</script>{{custom | safe}}
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
10
templates/donate_coin.ascii
Normal file
@@ -0,0 +1,10 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
Here is my [1m{{ coin }}[0m address if you'd like to send a donation 💙
|
||||
[1m{{ address }}[0m
|
||||
|
||||
Thank you for your support! 🙏
|
||||
|
||||
13
templates/donate_more.ascii
Normal file
@@ -0,0 +1,13 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m DONATE [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
Here is a list of additional cryptocurrencies and donation methods 💙
|
||||
For each coin below, you can get the address from [1m/donate/<coin>[0m
|
||||
|
||||
{% for coin in coins %}{% if loop.index0 % 4 == 0 and loop.index0 != 0 %}
|
||||
{% endif %}[1m{{ coin }}[0m{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
|
||||
Thank you for your support! 🙏
|
||||
|
||||
9
templates/error.ascii
Normal file
@@ -0,0 +1,9 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;31m ERROR: {{ error.code }} [0m
|
||||
[1;36m────────────[0m
|
||||
|
||||
[1;31m{{ error.message }}[0m
|
||||
|
||||
If you believe this is an error, please contact me via my socials listed at /contact
|
||||
|
||||
25
templates/favicon.ascii
Normal file
@@ -0,0 +1,25 @@
|
||||
▒▒▒ ▓▓▓
|
||||
▒░░░░▒▓ ▓▓▓▓▓▓▓
|
||||
▒░░░░░░▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░░░░▒▒▒▒▒▒▒ ▓▓▒▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░░▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒░░▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒ ▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒ ▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒ ▒▒▒▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒ ▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▒▒▒▒ ▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█
|
||||
▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▓█
|
||||
▓▓▓▓ ▓▓▓█
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/gitpgp">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/gitpgp">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,9 +29,11 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
@@ -48,6 +51,7 @@
|
||||
<p style="margin-top: 3em;">All commits changing this page have been signed with my PGP key as shown on Keybase <a href="https://keybase.io/nathanwoodburn" target="_blank">https://keybase.io/nathanwoodburn</a></p>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
13
templates/header.ascii
Normal file
@@ -0,0 +1,13 @@
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
[1;36m . . , . . . .. / [0m
|
||||
[1;36m |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / [0m
|
||||
[1;36m | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ [0m
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
|
||||
Home [/]
|
||||
Contact [/contact]
|
||||
Projects [/projects]
|
||||
Tools [/tools]
|
||||
Donate [/donate]
|
||||
Now [/now]
|
||||
|
||||
391
templates/hosting.html
Normal file
@@ -0,0 +1,391 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en-au" style="height: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/hosting">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/hosting">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77" style="display: flex;flex-direction: column;">{{handshake_scripts | safe}}
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="text-center content-section" style="background: #110033;padding-bottom: 100px;flex: 1;display: flex;flex-direction: column;justify-content: center;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>Web Hosting</h2>
|
||||
<p style="margin-bottom: 10px;">Configure your hosting setup below</p><form id="costForm">
|
||||
<div style="padding: 10px;">
|
||||
<label style="font-weight: bold; margin-bottom: 15px; display: block;">Choose a plan:</label>
|
||||
<div style="display: flex; gap: 15px; flex-wrap: wrap; margin-top: 10px;">
|
||||
<div class="plan-card" onclick="selectPreset('standard')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="standard" onchange="handlePresetChange()" checked style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Standard WordPress</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #007bff; margin-bottom: 10px;">$6/month</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
|
||||
<li style="margin-bottom: 5px;">✓ 0.5GB RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ 10GB Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Perfect for small sites</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="plan-card" onclick="selectPreset('premium')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="premium" onchange="handlePresetChange()" style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Premium WordPress</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #28a745; margin-bottom: 10px;">$10/month</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
|
||||
<li style="margin-bottom: 5px;">✓ 1GB RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ 50GB Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Weekly Backups Included</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="plan-card" onclick="selectPreset('custom')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
|
||||
<input type="radio" name="preset" value="custom" onchange="handlePresetChange()" style="margin-bottom: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">Custom Configuration</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #6c757d; margin-bottom: 10px;">Variable</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
|
||||
<li style="margin-bottom: 5px;">✓ Choose your CPU</li>
|
||||
<li style="margin-bottom: 5px;">✓ Choose your RAM</li>
|
||||
<li style="margin-bottom: 5px;">✓ Choose your Storage</li>
|
||||
<li style="margin-bottom: 5px;">✓ Optional Backups</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="customSliders" style="display: none;">
|
||||
<div>
|
||||
<label class="form-label" for="cpus" style="padding: 10px;">CPUs:
|
||||
<span id="cpusValue" style="display:inline-block; min-width: 3ch; font-family: monospace;">1</span>
|
||||
</label>
|
||||
<input id="cpus" name="cpus" class="form-range" type="range"
|
||||
min="1" max="4" value="1" step="1"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('cpus')">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="memory" style="padding: 10px;">Memory (GB):
|
||||
<span id="memoryValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">0.5</span>
|
||||
</label>
|
||||
<input id="memory" name="memory" class="form-range" type="range"
|
||||
min="0" max="6" value="0" step="1"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('memory')">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="disk" style="padding: 10px;">Disk Space (GB):
|
||||
<span id="diskValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">10</span>
|
||||
</label>
|
||||
<input id="disk" name="disk" class="form-range" type="range"
|
||||
min="10" max="500" value="10" step="10"
|
||||
style="max-width: 500px; padding-top: 10px;"
|
||||
oninput="updateValue('disk')">
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px;">
|
||||
<label><input type="checkbox" id="backups" onchange="calculateCost()"> Backups ($5/month for up to 150GB)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px; font-weight: bold;">
|
||||
Monthly Cost: $<span id="monthlyCost" style="display:inline-block; min-width: 6ch; font-family: monospace; text-align: left;">0.00</span><br>
|
||||
<small>All prices are in AUD</small>
|
||||
<br>
|
||||
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="Your Email" required style="margin-top: 10px;">
|
||||
<!-- Allow message -->
|
||||
<textarea id="message" name="message" class="form-control" placeholder="Your Message" rows="3" style="margin-top: 10px;"></textarea>
|
||||
|
||||
<button type="button" class="btn btn-primary" style="margin-top: 10px;" onclick="sendEnquiry()">Send Enquiry</button>
|
||||
<small id="enquiryStatus" style="display: block; margin-top: 10px;"></small>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Rates
|
||||
const cpuRate = 3;
|
||||
const memoryRate = 5;
|
||||
const diskRate = 0.1;
|
||||
const backupRate = 1;
|
||||
|
||||
// Memory values mapping
|
||||
const memoryValues = [0.5, 1, 2, 4, 8, 16, 24];
|
||||
|
||||
// Initialize on load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Check if parameters are in the URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Check for preset parameter
|
||||
if (urlParams.has('preset')) {
|
||||
const presetValue = urlParams.get('preset');
|
||||
const presetRadio = document.querySelector(`input[name="preset"][value="${presetValue}"]`);
|
||||
if (presetRadio) {
|
||||
presetRadio.checked = true;
|
||||
handlePresetChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (urlParams.has('cpus')) {
|
||||
document.getElementById('cpus').value = urlParams.get('cpus');
|
||||
}
|
||||
if (urlParams.has('memory')) {
|
||||
const memoryParam = parseFloat(urlParams.get('memory'));
|
||||
const memoryIndex = memoryValues.indexOf(memoryParam);
|
||||
document.getElementById('memory').value = memoryIndex >= 0 ? memoryIndex : 0;
|
||||
}
|
||||
if (urlParams.has('disk')) {
|
||||
document.getElementById('disk').value = urlParams.get('disk');
|
||||
}
|
||||
if (urlParams.has('backups')) {
|
||||
document.getElementById('backups').checked = urlParams.get('backups') === 'true';
|
||||
}
|
||||
if (urlParams.has('email')) {
|
||||
document.getElementById('email').value = urlParams.get('email');
|
||||
}
|
||||
if (urlParams.has('message')) {
|
||||
document.getElementById('message').value = urlParams.get('message');
|
||||
}
|
||||
// Update the displayed values
|
||||
updateValue('cpus');
|
||||
updateValue('memory');
|
||||
updateValue('disk');
|
||||
calculateCost();
|
||||
// Send the enquiry if the form is submitted
|
||||
document.getElementById('costForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
sendEnquiry();
|
||||
});
|
||||
|
||||
// Initialize preset handling and card styles
|
||||
handlePresetChange();
|
||||
updateCardStyles();
|
||||
});
|
||||
|
||||
function updateValue(field) {
|
||||
let value = document.getElementById(field).value;
|
||||
|
||||
if (field === 'memory') {
|
||||
value = memoryValues[parseInt(value)];
|
||||
}
|
||||
|
||||
document.getElementById(field + 'Value').textContent = value;
|
||||
calculateCost();
|
||||
}
|
||||
|
||||
function selectPreset(presetValue) {
|
||||
document.querySelector(`input[name="preset"][value="${presetValue}"]`).checked = true;
|
||||
handlePresetChange();
|
||||
updateCardStyles();
|
||||
}
|
||||
|
||||
function updateCardStyles() {
|
||||
const cards = document.querySelectorAll('.plan-card');
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
|
||||
cards.forEach((card, index) => {
|
||||
const presetValues = ['standard', 'premium', 'custom'];
|
||||
const isSelected = presetValues[index] === selectedPreset;
|
||||
|
||||
if (isSelected) {
|
||||
card.style.borderColor = '#007bff';
|
||||
card.style.background = '#f8f9fa';
|
||||
card.style.transform = 'translateY(-2px)';
|
||||
card.style.boxShadow = '0 4px 12px rgba(0,123,255,0.15)';
|
||||
} else {
|
||||
card.style.borderColor = '#ddd';
|
||||
card.style.background = 'white';
|
||||
card.style.transform = 'translateY(0)';
|
||||
card.style.boxShadow = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handlePresetChange() {
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
const customSliders = document.getElementById('customSliders');
|
||||
|
||||
updateCardStyles();
|
||||
|
||||
if (selectedPreset === 'custom') {
|
||||
customSliders.style.display = 'block';
|
||||
} else {
|
||||
customSliders.style.display = 'none';
|
||||
|
||||
// Set preset values
|
||||
if (selectedPreset === 'standard') {
|
||||
document.getElementById('cpus').value = 1;
|
||||
document.getElementById('memory').value = 0; // 0.5GB
|
||||
document.getElementById('disk').value = 10;
|
||||
document.getElementById('backups').checked = false;
|
||||
} else if (selectedPreset === 'premium') {
|
||||
document.getElementById('cpus').value = 1;
|
||||
document.getElementById('memory').value = 1; // 1GB
|
||||
document.getElementById('disk').value = 50;
|
||||
document.getElementById('backups').checked = true;
|
||||
}
|
||||
|
||||
// Update displayed values
|
||||
updateValue('cpus');
|
||||
updateValue('memory');
|
||||
updateValue('disk');
|
||||
}
|
||||
|
||||
calculateCost();
|
||||
}
|
||||
|
||||
function calculateCost() {
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
|
||||
// For presets, use fixed pricing
|
||||
if (selectedPreset === 'standard') {
|
||||
document.getElementById('monthlyCost').textContent = '6.00';
|
||||
return;
|
||||
} else if (selectedPreset === 'premium') {
|
||||
document.getElementById('monthlyCost').textContent = '10.00';
|
||||
return;
|
||||
}
|
||||
|
||||
// Custom calculation
|
||||
const cpus = parseFloat(document.getElementById('cpus').value);
|
||||
const memoryIndex = parseInt(document.getElementById('memory').value);
|
||||
const memory = memoryValues[memoryIndex];
|
||||
const disk = parseFloat(document.getElementById('disk').value);
|
||||
|
||||
let monthlyCost = (cpus * cpuRate) + (memory * memoryRate) + (disk * diskRate);
|
||||
|
||||
if (document.getElementById('backups').checked) {
|
||||
const backupUnits = Math.ceil(disk / 50);
|
||||
monthlyCost += backupUnits * backupRate;
|
||||
}
|
||||
|
||||
document.getElementById('monthlyCost').textContent = monthlyCost.toFixed(2);
|
||||
}
|
||||
|
||||
function sendEnquiry() {
|
||||
// Ensure that the email field is filled
|
||||
const email = document.getElementById('email').value;
|
||||
if (!email) {
|
||||
document.getElementById('enquiryStatus').textContent = 'Please enter your email address.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect form data
|
||||
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
|
||||
const cpus = document.getElementById('cpus').value;
|
||||
const memoryIndex = parseInt(document.getElementById('memory').value);
|
||||
const memory = memoryValues[memoryIndex];
|
||||
const disk = document.getElementById('disk').value;
|
||||
const backups = document.getElementById('backups').checked ? 'Yes' : 'No';
|
||||
const message = document.getElementById('message').value;
|
||||
const enquiryData = {
|
||||
email: email,
|
||||
preset: selectedPreset,
|
||||
cpus: cpus,
|
||||
memory: memory,
|
||||
disk: disk,
|
||||
backups: backups,
|
||||
message: message
|
||||
};
|
||||
// Send the enquiry data to the server
|
||||
fetch('/hosting/send-enquiry', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(enquiryData)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
document.getElementById('enquiryStatus').textContent = 'Enquiry sent successfully!';
|
||||
document.getElementById('enquiryStatus').style.color = 'green';
|
||||
calculateCost();
|
||||
} else {
|
||||
document.getElementById('enquiryStatus').textContent = 'Failed to send enquiry. Please try again.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error sending enquiry:', error);
|
||||
document.getElementById('enquiryStatus').textContent = 'Error sending enquiry. Please check your network connection.';
|
||||
document.getElementById('enquiryStatus').style.color = 'red';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer style="background: #110033;">
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>{{custom | safe}}
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
46
templates/index.ascii
Normal file
@@ -0,0 +1,46 @@
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
[1;36m . . , . . . .. / [0m
|
||||
[1;36m |\ | _.-+-|_ _.._ | | _ _ _||_ . .._.._ / [0m
|
||||
[1;36m | \|(_] | [ )(_][ ) * |/\|(_)(_)(_][_)(_|[ [ )/ [0m
|
||||
[1;36m─────────────────────────────────────────────────────[0m
|
||||
|
||||
Home [/]
|
||||
Contact [/contact]
|
||||
Projects [/projects]
|
||||
Tools [/tools]
|
||||
Donate [/donate]
|
||||
Now [/now]
|
||||
API [/api/v1]
|
||||
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m ABOUT ME [0m
|
||||
[1;36m──────────[0m
|
||||
|
||||
Hi, I'm [1mNathan Woodburn[0m from Canberra, Australia.
|
||||
I've been homeschooled through Year 12 and am now studying a
|
||||
[1mBachelor of Computer Science[0m.
|
||||
|
||||
I love building random projects, so this site is always evolving.
|
||||
I'm also one of the founders of [1;36mHandshake AU[0m [https://hns.au],
|
||||
working to grow Handshake adoption across Australia.
|
||||
|
||||
I'm currently working on: {{ repo | safe }}
|
||||
{% if not spotify.message %}Currently listening to: [1;36m{{ spotify.song_name }}[0m by [1;36m{{ spotify.artist }}[0m{% endif %}
|
||||
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m SKILLS [0m
|
||||
[1;36m────────[0m
|
||||
|
||||
- Linux servers & CLI
|
||||
- DNS & DNSSEC
|
||||
- NGINX web servers
|
||||
- Programming:
|
||||
- Python 3
|
||||
- C#
|
||||
- Java
|
||||
- Bash
|
||||
|
||||
|
||||
Served to: {{ ip }}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
@@ -36,26 +37,40 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome5-overrides.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/index.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/swiper.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script><link rel="preload" as="image" href="/assets/img/bg/BlueMountains.jpg" type="image/jpeg">
|
||||
</head>
|
||||
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77">{{handshake_scripts | safe}}
|
||||
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77"><script>
|
||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
}
|
||||
</script>
|
||||
{{handshake_scripts | safe}}
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -64,9 +79,15 @@
|
||||
<div class="text-end d-none d-xl-block d-xxl-block" id="downtime"><blockquote class="speech bubble"><em>G'day!</em><br>
|
||||
Some services are down.<br>
|
||||
Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.webp"></div>
|
||||
<header class="masthead main" style="background: url("/assets/img/bg/main.webp") center / cover;position: relative;height: 400px;">
|
||||
<div class="intro-body text parallax" style="color: rgb(0,0,0);">
|
||||
<h1 class="inner-text" style="margin-bottom: 0px;">Nathan.Woodburn/</h1><span>{{message|safe}}</span>
|
||||
<header class="masthead main" style="position: relative;height: 400px;background: url("/assets/img/bg/BlueMountains.jpg") center / cover;">
|
||||
<div class="intro-body text parallax">
|
||||
<div class="name-container" style="padding-right: 1em;">
|
||||
<div class="slider">
|
||||
<h1>/</h1>
|
||||
</div>
|
||||
<h1 class="brand">Nathan.Woodburn</h1>
|
||||
</div>
|
||||
<div style="padding-top: 75px;"><span>{{message|safe}}</span></div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="text-center content-section" id="about" style="padding-bottom: 0px;background: black;">
|
||||
@@ -74,8 +95,8 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>About ME</h2>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.jpg" style="border-radius: 50%;" alt="My Profile"><img class="profile foreground" src="/assets/img/pfront.webp" alt=""></div>
|
||||
<p style="margin-bottom: 5px;">Hi, I'm Nathan Woodburn and I live in Canberra, Australia.<br>I've been home schooled all the way to Yr 12.<br>I'm currently studying a Bachelor of Computer Science.<br>I create tons of random projects so this site is often behind.<br>I'm one of the founders of <a href="https://hns.au" target="_blank">Handshake AU</a> working to increase Handshake adoption in Australia.<br>I work for <a href="https://www.namebase.io" target="_blank">Namebase</a> as tech and general support. Namebase is a US based company owned by <a href="https://namecheap.com" target="_blank">Namecheap</a>.</p>
|
||||
<div class="profile-container" style="margin-bottom: 2em;"><img class="profile background" src="/assets/img/profile.webp" style="border-radius: 50%;" alt="My Profile"><img class="profile foreground" src="/assets/img/pfront.webp" alt=""></div>
|
||||
<p style="margin-bottom: 5px;">Hi, I'm Nathan Woodburn and I live in Canberra, Australia.<br>I've been home schooled all the way to Yr 12.<br>I'm currently studying a Bachelor of Computer Science.<br>I create tons of random projects so this site is often behind.<br>I'm one of the founders of <a href="https://hns.au" target="_blank">Handshake AU</a> working to increase Handshake adoption in Australia.</p>
|
||||
<p title="{{repo_description}}" style="margin-bottom: 0px;display: inline-block;">I'm currently working on</p>
|
||||
<p data-bs-toggle="tooltip" data-bss-tooltip="" title="{{repo_description}}" style="display: inline-block;">{{repo | safe}}</p>
|
||||
</div>
|
||||
@@ -84,12 +105,16 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>Skills</h2>
|
||||
<ul class="list-unstyled" style="font-size: 18px;">
|
||||
<li class="printing">3D Printing</li>
|
||||
<li>Autodesk Fusion 360 (CAD Modeling)</li>
|
||||
<li class="programc">Programming with various languages</li>
|
||||
<li>DNS, DNSSEC and Trustless SSL</li>
|
||||
<li class="programlinux">Linux Servers and CLI</li>
|
||||
<li>DNS and DNSSEC</li>
|
||||
<li class="programnginx">NGINX Web Servers</li>
|
||||
<li class="programc">Programming in<ul class="list-inline">
|
||||
<li class="list-inline-item">Python 3</li>
|
||||
<li class="list-inline-item">C#</li>
|
||||
<li class="list-inline-item">Java</li>
|
||||
<li class="list-inline-item">Bash</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,7 +127,7 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="swiper">
|
||||
<div class="swiper-wrapper">{% for project in projects %}
|
||||
<div class="swiper-slide site" data-url="{{ project.html_url }}">
|
||||
<img class="site-img" src="{{ project.avatar_url }}" />
|
||||
<img class="site-img" src="{{ project.avatar_url }}" alt="{{ project.name }} Icon" />
|
||||
<div class="site-body">
|
||||
<div class="site-detail" style="width: 100%;">
|
||||
<h2 class="site-name" style="text-align: left;">{{ project.name }}</h2>
|
||||
@@ -203,13 +228,13 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p>Verify me with this <a href="pgp" target="_blank">long lifetime Public Key</a> or this <a href="gitpgp" target="_blank">short term one for Github commits</a></p>
|
||||
<p>Verify me with this <a href="pgp" target="_blank">PGP Public Key</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block">This site is also available on<br><a href="https://learn.namebase.io/" target="_blank">Handshake</a> at <a href="https://nathan.woodburn">https://nathan.woodburn/</a></p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2024</p>
|
||||
<p class="copyright">Copyright © Nathan.Woodburn/ 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,9 +292,263 @@ Check them out here!</blockquote><img class="img-fluid" src="/assets/img/pfront.
|
||||
<div class="d-none d-print-none d-sm-none d-md-block d-lg-block d-xl-block d-xxl-block clock" style="padding: 1em;background: #10101039;border-top-right-radius: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20 20" fill="none" class="fs-2">
|
||||
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18ZM11 6C11 5.44772 10.5523 5 10 5C9.44771 5 9 5.44772 9 6V10C9 10.2652 9.10536 10.5196 9.29289 10.7071L12.1213 13.5355C12.5118 13.9261 13.145 13.9261 13.5355 13.5355C13.9261 13.145 13.9261 12.5118 13.5355 12.1213L11 9.58579V6Z" fill="currentColor"></path>
|
||||
</svg><span style="margin-left: 10px;font-family: 'Anonymous Pro', monospace;">{{time|safe}}</span></div>
|
||||
</svg><span style="margin-left: 10px;font-family: 'Anonymous Pro', monospace;">{{time|safe}}</span></div><!-- Pop-out button for mobile -->
|
||||
<button id="spotify-toggle" style="
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.5s ease;
|
||||
transform: translateX(200%); /* start hidden off-screen *
|
||||
">
|
||||
<img src="/assets/img/external/spotify.png" alt="Spotify" style="
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
"></img>
|
||||
</button>
|
||||
|
||||
<div id="spotify-widget" style="
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #121212;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
font-family: sans-serif;
|
||||
max-width: 300px;
|
||||
z-index: 9999;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
opacity: 0.9;
|
||||
transform: translateX(120%); /* start hidden off-screen */
|
||||
">
|
||||
<img id="spotify-album-art" src="" alt="Album Art" style="
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 6px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
">
|
||||
<div style="flex: 1; overflow: hidden;">
|
||||
<div id="spotify-song" style="
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
"></div>
|
||||
<div id="spotify-artist" style="
|
||||
font-size: 0.85rem;
|
||||
color: #ccc;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
"></div>
|
||||
<div id="spotify-album" style="
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
"></div>
|
||||
<!-- Progress Bar -->
|
||||
<div style="
|
||||
margin-top: 6px;
|
||||
height: 4px;
|
||||
background: #333;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
">
|
||||
<div id="spotify-progress" style="
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background: #1DB954;
|
||||
transition: width 1s linear;
|
||||
"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const widget = document.getElementById('spotify-widget');
|
||||
const toggleBtn = document.getElementById('spotify-toggle');
|
||||
|
||||
function isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
function updateVisibility() {
|
||||
if(isMobile()){
|
||||
widget.style.transform = 'translateX(120%)'; // hidden off-screen
|
||||
toggleBtn.style.transform = 'translateX(0)'; // visible
|
||||
} else {
|
||||
widget.style.transform = 'translateX(0)'; // visible
|
||||
toggleBtn.style.transform = 'translateX(200%)'; // hidden off-screen
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle widget slide in/out on mobile
|
||||
toggleBtn.addEventListener('click', (e) => {
|
||||
widget.style.transform = 'translateX(0)'; // slide in
|
||||
toggleBtn.style.transform = 'translateX(200%)'; // hide button
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Close widget when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if(isMobile()){
|
||||
if(!widget.contains(e.target) && e.target !== toggleBtn){
|
||||
widget.style.transform = 'translateX(120%)'; // slide out
|
||||
toggleBtn.style.transform = 'translateX(0)'; // show button
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent clicks inside widget from closing it
|
||||
widget.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Variable to track progress bar animation
|
||||
let progressInterval = null;
|
||||
let progressSpeed = 0;
|
||||
let lastUpdateTime = Date.now();
|
||||
let currentProgress = 0;
|
||||
let targetProgress = 0;
|
||||
let trackDuration = 0;
|
||||
let currentTrackId = null;
|
||||
|
||||
// --- Spotify fetch ---
|
||||
async function updateSpotifyWidget() {
|
||||
try {
|
||||
const res = await fetch('/api/v1/playing');
|
||||
if (!res.ok) return;
|
||||
|
||||
const data = await res.json();
|
||||
// Check if data contains an error or message indicating nothing is playing
|
||||
if (data.error || data.message) {
|
||||
// If existing data
|
||||
if (document.getElementById('spotify-song').textContent) {
|
||||
return;
|
||||
}
|
||||
// Alternate text when nothing is playing
|
||||
document.getElementById('spotify-album-art').src = '/assets/img/external/spotify.png';
|
||||
document.getElementById('spotify-song').textContent = 'Not Playing';
|
||||
document.getElementById('spotify-artist').textContent = '';
|
||||
document.getElementById('spotify-album').textContent = '';
|
||||
document.getElementById('spotify-progress').style.width = '0%';
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
currentProgress = 0;
|
||||
currentTrackId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const track = data.spotify;
|
||||
var firstLoad = false;
|
||||
// Check if this is the first time loading data
|
||||
if (!document.getElementById('spotify-song').textContent) {
|
||||
firstLoad = true;
|
||||
}
|
||||
|
||||
// Check if track has changed (new song started)
|
||||
const trackId = track.song_name + track.artist; // Simple track identifier
|
||||
const isNewTrack = currentTrackId !== null && currentTrackId !== trackId;
|
||||
|
||||
if (isNewTrack) {
|
||||
// Reset progress bar instantly for new track
|
||||
currentProgress = 0;
|
||||
document.getElementById('spotify-progress').style.transition = 'none';
|
||||
document.getElementById('spotify-progress').style.width = '0%';
|
||||
// Force reflow to apply the instant reset
|
||||
document.getElementById('spotify-progress').offsetHeight;
|
||||
// Re-enable transition
|
||||
document.getElementById('spotify-progress').style.transition = 'width 0.1s linear';
|
||||
}
|
||||
|
||||
currentTrackId = trackId;
|
||||
|
||||
|
||||
document.getElementById('spotify-album-art').src = track.album_art;
|
||||
document.getElementById('spotify-song').textContent = track.song_name;
|
||||
document.getElementById('spotify-artist').textContent = track.artist;
|
||||
document.getElementById('spotify-album').textContent = track.album_name;
|
||||
|
||||
// Update progress bar
|
||||
if (track.is_playing) {
|
||||
currentProgress = (track.progress_ms / track.duration_ms) * 100;
|
||||
trackDuration = track.duration_ms;
|
||||
lastUpdateTime = Date.now();
|
||||
|
||||
document.getElementById('spotify-progress').style.width = currentProgress + '%';
|
||||
|
||||
// Clear existing interval
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
// Start interval to animate progress bar
|
||||
progressInterval = setInterval(animateProgressBar, 100);
|
||||
} else {
|
||||
document.getElementById('spotify-progress').style.width = currentProgress + '%';
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
|
||||
// If first load and desktop, slide in the widget
|
||||
if (firstLoad) {
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch Spotify data', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Animate progress bar
|
||||
function animateProgressBar() {
|
||||
if (trackDuration === 0) return;
|
||||
|
||||
const now = Date.now();
|
||||
const elapsed = now - lastUpdateTime;
|
||||
lastUpdateTime = now;
|
||||
|
||||
// Calculate progress increment based on elapsed time
|
||||
const progressIncrement = (elapsed / trackDuration) * 100;
|
||||
currentProgress += progressIncrement;
|
||||
|
||||
if (currentProgress >= 100) {
|
||||
currentProgress = 100;
|
||||
document.getElementById('spotify-progress').style.width = '100%';
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
// Refresh API when progress reaches 100%
|
||||
setTimeout(updateSpotifyWidget, 500);
|
||||
} else {
|
||||
document.getElementById('spotify-progress').style.width = currentProgress + '%';
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for Spotify API to have responded before initial display
|
||||
updateSpotifyWidget();
|
||||
|
||||
window.addEventListener('resize', updateVisibility);
|
||||
setInterval(updateSpotifyWidget, 15000);
|
||||
</script>
|
||||
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
<script src="/assets/js/downtime.min.js"></script>
|
||||
<script src="/assets/js/pfp.min.js"></script>
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/link">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/link">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,13 +29,15 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/line-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/animate.min.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/GridSystem-1.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
@@ -101,6 +104,7 @@
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/loading">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/loading">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -28,10 +29,9 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/loading.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
6
templates/now.ascii
Normal file
@@ -0,0 +1,6 @@
|
||||
{{header}}
|
||||
[1;36m───────────────────────────────────────────────[0m
|
||||
[1;36m Now {{ date }} [0m
|
||||
[1;36m────────────[0m
|
||||
|
||||
{{content | safe}}
|
||||
@@ -9,15 +9,16 @@
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:title" content="Nathan.Woodburn/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:title" content="Nathan.Woodburn/">
|
||||
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
|
||||
@@ -29,9 +30,11 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
@@ -39,6 +42,7 @@
|
||||
<body>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_02_18.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_02_18.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_02_18">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_02_18">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta name="twitter:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,23 +32,32 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/now.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -156,6 +166,7 @@ Find out what I've been up to in the last week">
|
||||
</section>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_02_25.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_02_25.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_02_25">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_02_25">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,23 +32,32 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/now.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -123,6 +133,7 @@ Find out what I've been up to in the last week">
|
||||
</section>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_04_19.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_04_19.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_04_19">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_04_19">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,23 +32,32 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/now.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -136,6 +146,7 @@ Find out what I've been up to in the last week">
|
||||
</section>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_05_20.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_05_20.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_05_20">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_05_20">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -105,6 +115,7 @@ Find out what I've been up to in the last week">
|
||||
</section>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_05_28.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_05_28.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_05_28">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_05_28">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -105,6 +115,7 @@ Find out what I've been up to in the last week">
|
||||
</section>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_06.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_06.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_06">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_06">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -121,6 +131,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_17.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_17.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_17">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_17">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -121,6 +131,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_24.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_24.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_06_24">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_06_24">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -121,6 +131,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_07_15.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_07_15.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_07_15">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_07_15">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -121,6 +131,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_08_05.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_08_05.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_08_05">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_08_05">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -150,6 +160,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_09_07.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_09_07.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_09_07">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_09_07">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -158,6 +168,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_09_27.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_09_27.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_09_27">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_09_27">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -156,6 +166,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_07.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_07.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_07">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_07">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -168,6 +178,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_15.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_15.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_15">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_15">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -150,6 +160,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>What's up at the moment | Nathan.Woodburn/</title>
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_22.html">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_22.html">
|
||||
<link rel="canonical" href="https://nathan.woodburn.au/now/24_10_22">
|
||||
<meta property="og:url" content="https://nathan.woodburn.au/now/24_10_22">
|
||||
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
|
||||
<meta property="og:description" content="G'day,
|
||||
Find out what I've been up to in the last week">
|
||||
<meta property="og:title" content="What's up at the moment | Nathan.Woodburn/">
|
||||
@@ -31,22 +32,31 @@ Find out what I've been up to in the last week">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/profile.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
|
||||
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
|
||||
</head>
|
||||
|
||||
<body class="text-center" style="background: linear-gradient(rgba(0,0,0,0.80), rgba(0,0,0,0.80)), url("/assets/img/bg/background.webp") center / cover no-repeat;">
|
||||
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
|
||||
<div class="container-fluid"><a class="navbar-brand nathanwoodburn" href="/#">Nathan.Woodburn/</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="container-fluid"><a class="navbar-brand" href="/#">
|
||||
<div style="padding-right: 1em;display: inline-flex;">
|
||||
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
|
||||
</div>
|
||||
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
|
||||
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -150,6 +160,7 @@ Find out what I've been up to in the last week">
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
<script src="/assets/js/grayscale.min.js"></script>
|
||||
<script src="/assets/js/hacker.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||