Compare commits

...

6 Commits

Author SHA1 Message Date
9af115e403 Update to latest blog and update image for fixed code in SPA part 6
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-05-20 01:33:29 +00:00
5105d9e582 Update with fix to Bergamot typo
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-05-19 05:40:11 +00:00
4e982e6a2b Add flake lock with most recent website
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-05-14 07:38:51 +00:00
469bdf7389 Update screenshots with sidenote chomping changes 2026-05-14 06:59:46 +00:00
5b710c7b67 Update test files with new tag margins 2026-05-14 01:11:00 +00:00
3b39bacc80 Add a script I used to check that my tag spacing changes
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-05-13 17:43:02 -07:00
72 changed files with 150 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 KiB

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

After

Width:  |  Height:  |  Size: 906 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 KiB

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 KiB

After

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 KiB

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 KiB

After

Width:  |  Height:  |  Size: 956 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 KiB

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 KiB

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 KiB

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

After

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 KiB

After

Width:  |  Height:  |  Size: 933 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

After

Width:  |  Height:  |  Size: 543 KiB

47
flake.lock generated
View File

@@ -31,15 +31,16 @@
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"nixpkgs-unstable": "nixpkgs-unstable",
"resume": "resume", "resume": "resume",
"web-files": "web-files" "web-files": "web-files"
}, },
"locked": { "locked": {
"lastModified": 1768847023, "lastModified": 1779238599,
"narHash": "sha256-r9ah5RgR00FyZ9HENdyY52cK8T+Bw0YYitOwSHpng0Q=", "narHash": "sha256-ZfuJmoP8aMt4ecCirmRTgx7mAKqgjaWjNZs0Rc6WKGs=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "78213700c8b858e0f92c4ff77d5c3c6dc4fe690d", "rev": "4481c38cf3bed9a110b698afde2d6f7da02cb815",
"revCount": 227, "revCount": 238,
"type": "git", "type": "git",
"url": "https://dev.danilafe.com/Nix-Configs/blog-static-flake.git" "url": "https://dev.danilafe.com/Nix-Configs/blog-static-flake.git"
}, },
@@ -51,11 +52,11 @@
"blog-source": { "blog-source": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1768846982, "lastModified": 1779238547,
"narHash": "sha256-q8lz1UUvA4D3e2Ankqj1naDxIykVt5k4OX5HjlHS1gY=", "narHash": "sha256-rXs/xEhHugC5Z0ARzmShqEbXnKBEdg2NE+7M/zMHmQw=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "4d35ca04fea950136d59a371942f84044b87b513", "rev": "98e2e7da6c9b87df24ea42ec76fb955ad6c59950",
"revCount": 927, "revCount": 958,
"submodules": true, "submodules": true,
"type": "git", "type": "git",
"url": "https://dev.danilafe.com/Web-Projects/blog-static.git" "url": "https://dev.danilafe.com/Web-Projects/blog-static.git"
@@ -158,11 +159,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1768773494, "lastModified": 1778430510,
"narHash": "sha256-XsM7GP3jHlephymxhDE+/TKKO1Q16phz/vQiLBGhpF4=", "narHash": "sha256-Ti+ZBvW6yrWWAg2szExVTwCd4qOJ3KlVr1tFHfyfi8Q=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "77ef7a29d276c6d8303aece3444d61118ef71ac2", "rev": "8fd9daa3db09ced9700431c5b7ad0e8ba199b575",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -172,6 +173,22 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-unstable": {
"locked": {
"lastModified": 1777954456,
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"resume": { "resume": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
@@ -181,11 +198,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1766794825, "lastModified": 1778642986,
"narHash": "sha256-+Fabo0uQF9srEXdi9TB1wjB+PHaU9htXj/fjnvUFNAs=", "narHash": "sha256-+0DskouobfQ0eEaJRzWACvjHswnRtdXMw4wl1Y/TLNg=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "f22cb5b79580020079ba7223346e612b41eb7d42", "rev": "7f8d95c5e40ce63f7173d781d494ae44142a862c",
"revCount": 74, "revCount": 76,
"type": "git", "type": "git",
"url": "https://dev.danilafe.com/DanilaFe/resume" "url": "https://dev.danilafe.com/DanilaFe/resume"
}, },

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
from pathlib import Path
from PIL import Image, ImageChops
import argparse
import re
def load_rgb(path: Path) -> Image.Image:
return Image.open(path).convert("RGB")
def mse(img: Image.Image) -> float:
hist = img.histogram()
sq = sum(v * ((i % 256) ** 2) for i, v in enumerate(hist))
return sq / (img.width * img.height * 3)
def crop_pair(expected, actual, shift):
# shift > 0 means actual content is lower than expected.
w = min(expected.width, actual.width)
if shift >= 0:
e_box = (0, 0, w, min(expected.height, actual.height - shift))
a_box = (0, shift, w, shift + (e_box[3] - e_box[1]))
else:
s = -shift
a_box = (0, 0, w, min(actual.height, expected.height - s))
e_box = (0, s, w, s + (a_box[3] - a_box[1]))
return expected.crop(e_box), actual.crop(a_box)
def best_vertical_shift(expected, actual, max_shift=100):
best = None
for shift in range(18, 19):
e, a = crop_pair(expected, actual, shift)
if e.height <= 0:
continue
score = mse(ImageChops.difference(e, a))
if best is None or score < best[1]:
best = (shift, score)
return best
def changed_until_y(expected, actual, threshold=5, min_changed_pixels=10):
diff = ImageChops.difference(expected, actual)
last_changed = None
for y in range(diff.height):
row = diff.crop((0, y, diff.width, y + 1)).convert("L")
changed = sum(1 for px in row.getdata() if px > threshold)
if changed >= min_changed_pixels:
last_changed = y
return {
"last_changed_y": last_changed,
"clean_after_y": 0 if last_changed is None else last_changed + 1,
}
def find_pairs(root: Path):
expected_files = sorted(root.rglob("*-expected.png"))
for expected in expected_files:
actual = expected.with_name(
expected.name.replace("-expected.png", "-actual.png")
)
if actual.exists():
yield expected, actual
def main():
ap = argparse.ArgumentParser()
ap.add_argument("root", type=Path, nargs="?", default=Path("test-results"))
ap.add_argument("--max-shift", type=int, default=100)
ap.add_argument("--make-diffs", action="store_true")
args = ap.parse_args()
failed = False
for expected_path, actual_path in find_pairs(args.root):
expected = load_rgb(expected_path)
actual = load_rgb(actual_path)
raw_score = mse(ImageChops.difference(
expected.crop((0, 0, min(expected.width, actual.width), min(expected.height, actual.height))),
actual.crop((0, 0, min(expected.width, actual.width), min(expected.height, actual.height))),
))
shift, shifted_score = best_vertical_shift(
expected, actual, max_shift=args.max_shift
)
status = "OK" if shifted_score < raw_score * 0.05 else "CHECK"
if status == "CHECK":
failed = True
rel = expected_path.relative_to(args.root)
print(f"{status} {rel}")
print(f" expected: {expected.size}, actual: {actual.size}")
print(f" best shift: {shift:+d}px")
print(f" raw mse: {raw_score:.3f}")
print(f" shifted mse: {shifted_score:.3f}")
if args.make_diffs:
e, a = crop_pair(expected, actual, shift)
change_info = changed_until_y(e, a, threshold=2)
print(f" last changed y after shift: {change_info['last_changed_y']}")
print(f" clean after y after shift: {change_info['clean_after_y']}")
diff = ImageChops.difference(e, a)
out = expected_path.with_name(
expected_path.name.replace("-expected.png", f"-shift-{shift:+d}-diff.png")
)
diff.save(out)
print(f" wrote: {out}")
raise SystemExit(1 if failed else 0)
if __name__ == "__main__":
main()