Account for remaining site changes

This commit is contained in:
Zachery Aaron Shores-Chmielewski 2026-07-24 12:51:40 +04:00
parent 0717383be9
commit 8ce7b655bc
8 changed files with 123 additions and 36 deletions

View file

@ -11,9 +11,9 @@ pub fn ocean_palette(root_index: u8, iterations: u32, max_iterations: u32) -> (u
// Ocean theme colors for each root basin
let (r, g, b) = match root_index {
0 => (0.05, 0.25, 0.55), // Deep ocean blue
1 => (0.0, 0.45, 0.50), // Teal
2 => (0.15, 0.55, 0.65), // Cyan/turquoise
0 => (0.05, 0.25, 0.55), // Deep ocean blue
1 => (0.0, 0.45, 0.50), // Teal
2 => (0.15, 0.55, 0.65), // Cyan/turquoise
_ => (0.05, 0.25, 0.55),
};

View file

@ -16,9 +16,9 @@ pub fn compute(z_re: f64, z_im: f64, time: f64) -> (u8, u32) {
// The three roots of z^3 - 1
const ROOTS: [(f64, f64); 3] = [
(1.0, 0.0), // 1
(-0.5, 0.866025403784439), // e^(2πi/3)
(-0.5, -0.866025403784439), // e^(4πi/3)
(1.0, 0.0), // 1
(-0.5, 0.866025403784439), // e^(2πi/3)
(-0.5, -0.866025403784439), // e^(4πi/3)
];
for iter in 0..MAX_ITERATIONS {

View file

@ -7,7 +7,9 @@ use crate::newton::compute;
const MAX_ITERATIONS: u32 = 32;
/// Get the canvas element and 2D context
fn get_canvas_and_context(canvas_id: &str) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), JsValue> {
fn get_canvas_and_context(
canvas_id: &str,
) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), JsValue> {
let window = web_sys::window().ok_or("no window")?;
let document = window.document().ok_or("no document")?;
let canvas = document
@ -66,7 +68,8 @@ pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result
// Create ImageData at reduced size
let clamped = wasm_bindgen::Clamped(&pixels[..]);
let image_data = ImageData::new_with_u8_clamped_array_and_sh(clamped, width as u32, height as u32)?;
let image_data =
ImageData::new_with_u8_clamped_array_and_sh(clamped, width as u32, height as u32)?;
// Scale up when drawing to canvas
if scale > 1 {
@ -89,8 +92,10 @@ pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result
)?;
context.draw_image_with_html_canvas_element_and_dw_and_dh(
&temp_canvas,
0.0, 0.0,
canvas_width as f64, canvas_height as f64
0.0,
0.0,
canvas_width as f64,
canvas_height as f64,
)?;
} else {
context.put_image_data(&image_data, 0.0, 0.0)?;

View file

@ -2,17 +2,18 @@
WEB_UI_PORT=3001
SSH_PORT=2223
DATA_DIR=/home/deploy/forgejo/data
docker run -d \
--name forgejo \
-p ${WEB_UI_PORT}:3000 \
-p 127.0.0.1:${WEB_UI_PORT}:3000 \
-p ${SSH_PORT}:22 \
-v ~/forgejo/data:/data \
-v ${DATA_DIR}:/data \
-v /etc/timezone:/etc/timezone:ro \
-v /etc/localtime:/etc/localtime:ro \
--restart always \
--restart always \
codeberg.org/forgejo/forgejo:9
echo "Forgejo started on:"
echo " Web UI: http://zachery.lol:${WEB_UI_PORT}"
echo " Web UI: http://127.0.0.1:${WEB_UI_PORT} (proxied at https://zachery.lol/code/)"
echo " ssh port: ${SSH_PORT}"

View file

@ -33,8 +33,8 @@ server {
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl ipv6only=on; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/zachery.lol/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/zachery.lol/privkey.pem; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/zachery.lol-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/zachery.lol-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

View file

@ -117,6 +117,8 @@
}
}
</style>
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
</head>
<body>
<canvas id="fractal-canvas"></canvas>
@ -135,15 +137,7 @@
</nav>
</div>
<script>
requestAnimationFrame(function () {
requestAnimationFrame(function () {
var s = document.createElement('script');
s.src = '/routes/root/fractal-gl.js?v=9';
s.async = true;
document.body.appendChild(s);
});
});
</script>
<!-- fractal background loaded via deferred <script> in <head> -->
</body>
</html>

View file

@ -279,6 +279,8 @@
}
}
</script>
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
</head>
<body>
<canvas id="fractal-canvas"></canvas>
@ -323,15 +325,7 @@
<a href="/contact">back to contact</a>
</nav>
<script>
requestAnimationFrame(function () {
requestAnimationFrame(function () {
var s = document.createElement('script');
s.src = '/routes/root/fractal-gl.js?v=9';
s.async = true;
document.body.appendChild(s);
});
});
</script>
<!-- fractal background loaded via deferred <script> in <head> -->
</body>
</html>

93
tools/gen-strips.py Normal file
View file

@ -0,0 +1,93 @@
# /// script
# requires-python = ">=3.9"
# dependencies = ["pillow"]
# ///
"""
Generate routes/root/dist-strips.bin from routes/root/dist.png.
Splits the 512x512 grayscale Julia distance field into N horizontal bands,
re-encodes each band as its own WebP, and concatenates them in CENTER-OUT order
(the band the pan/zoom animation shows first) so a streaming client can upload
the visible region before the outer bands have finished downloading. WebP q90 is
~half the size of PNG with no visible loss after the color-band mapping.
File layout (little-endian):
u32 headerLen
bytes headerLen -- UTF-8 JSON: {fullW, fullH, bands:[{y,h,off,len}, ...]}
bytes payload -- concatenated band WebPs in the order listed in `bands`
(off/len are byte ranges within payload, stream order)
Re-run only when dist.png changes; the output is committed.
Usage: uv run tools/gen-strips.py
"""
import io
import json
import os
import struct
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
SRC = os.path.join(ROOT, "routes", "root", "dist.png")
OUT = os.path.join(ROOT, "routes", "root", "dist-strips.bin")
NBANDS = 8
WEBP_QUALITY = 90 # lossy; ~half of PNG, visually identical. Bump to 95 / use lossless if needed.
def center_out(n):
"""Band indices ordered from the middle outward: e.g. n=8 -> [4,3,5,2,6,1,7,0]."""
order = []
lo, hi = n // 2 - 1, n // 2
while lo >= 0 or hi < n:
if hi < n:
order.append(hi)
hi += 1
if lo >= 0:
order.append(lo)
lo -= 1
return order
def main():
img = Image.open(SRC).convert("L")
w, h = img.size
# Row boundaries (robust if h is not divisible by NBANDS).
bounds = [round(i * h / NBANDS) for i in range(NBANDS + 1)]
bands_by_index = []
for i in range(NBANDS):
y0, y1 = bounds[i], bounds[i + 1]
crop = img.crop((0, y0, w, y1))
buf = io.BytesIO()
crop.save(buf, format="WEBP", quality=WEBP_QUALITY, method=6)
bands_by_index.append((y0, y1 - y0, buf.getvalue()))
payload = bytearray()
meta = []
order = center_out(NBANDS)
for idx in order:
y, bh, png = bands_by_index[idx]
meta.append({"y": y, "h": bh, "off": len(payload), "len": len(png)})
payload += png
header = json.dumps(
{"fullW": w, "fullH": h, "bands": meta}, separators=(",", ":")
).encode("utf-8")
with open(OUT, "wb") as f:
f.write(struct.pack("<I", len(header)))
f.write(header)
f.write(payload)
total = 4 + len(header) + len(payload)
src_size = os.path.getsize(SRC)
print(f"{w}x{h} -> {NBANDS} bands, center-out order {order}")
print(
f"header {len(header)}B, payload {len(payload)}B, total {total}B "
f"({total / 1024:.1f} KB) vs dist.png {src_size}B ({src_size / 1024:.1f} KB)"
)
print(f"wrote {OUT}")
if __name__ == "__main__":
main()