diff --git a/python/tests/fixtures/social/README.md b/python/tests/fixtures/social/README.md
deleted file mode 100644
index 14cef61..0000000
--- a/python/tests/fixtures/social/README.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Social plugin compatibility matrix
-
-These projects compare the user-visible output of Material for MkDocs and
-Zensical with the same `mkdocs.yml` and source files. Run the matrix from the
-repository root:
-
-```console
-/Users/squidfunk/.workspace/squidfunk/repos/mkdocs-material/community/venv/bin/python \
- scripts/social_compatibility.py \
- --mkdocs /Users/squidfunk/.workspace/squidfunk/repos/mkdocs-material/community/venv/bin/mkdocs \
- --zensical .venv/bin/zensical
-```
-
-| Case | Behavior |
-| --- | --- |
-| `basic` | Custom layout, root and nested card routes, page title and metadata |
-| `filters` | Include precedence, exclude patterns, page-level opt-out |
-| `metadata` | Meta plugin inheritance and page-level layout options |
-| `multiple` | Ordered plugin instances, separate card directories and metadata |
-| `no-site-url` | Cards generated without public image metadata |
-| `image-only` | Bundled image-only layout and a local SVG dependency |
-| `bundled` | Default, accent, invert and variant layouts with typography |
-| `logo-icon` | Explicit theme logo icon and omitted font settings |
-| `debug` | Build-time debug grid and color settings |
-| `blog` | Cards for generated blog views and posts |
-| `disabled` | Disabled plugin stays disabled despite a page opt-in |
-| `cards-off` | Global card switch with a page-level opt-in |
-| `paths` | Flat URLs, alternate card directory and custom layout directory |
-| `layout-options` | Bundled-layout image, logo, title, description and page overrides |
-| `layer-composition` | YAML definitions, SVG background, icon, origins and offsets |
-| `theme-defaults` | Palette list, SVG/PNG logos, exact font face and variant fallback |
-| `custom-typography` | Custom alignment, wrapping, shrinking and line spacing |
-| `template-context` | Page file and URL values inside custom tag templates |
-| `lifecycle` | Cached rebuilds after image, page metadata and layout edits |
-| `recoverable-error` | Missing image with ignored card-generation error |
-| `deprecated` | Deprecated `cards_color` and `cards_font` settings |
-| `debug-no-grid` | Debug overlay without a grid |
-
-The check compares generated card paths, dimensions and every card's mean RGB
-pixel difference, plus each page's social metadata. A case passes when the
-metadata and dimensions match and every card's mean RGB difference is at most
-1 on a 0–255 scale. The `lifecycle` case also rebuilds both engines after each
-exact source edit and checks that the expected output changed. PNG file bytes
-may differ between image libraries even when the visible output is the same.
-Build logs and full output are kept in a temporary directory only while the
-command runs; pass `--output PATH` to keep them for inspection.
-
-The cases using typography or debug labels download Roboto from Google Fonts
-on their first build. Use `--font-cache PATH` to seed both temporary projects
-from an existing Material font cache and run without a network connection.
-
-On 2026-09-23, 19 of 22 cases passed the strict pixel threshold using Material
-for MkDocs 9.7.1 with Pillow 12.1.1 and this Zensical branch. One remaining
-case is an accepted rasterization difference; the other two are behavior gaps:
-
-- `custom-typography`: text placement matches visually. Different text
- rasterizers exceed the strict pixel threshold (maximum mean RGB difference
- 3.636), but this is not considered a user-visible defect.
-- `template-context`: Material exposes `page.file.src_uri` to layout templates;
- the Zensical build fails because that value is undefined.
-- `lifecycle`: after a cached SVG background edit, Material retains its old
- card while Zensical updates it. The initial build and later layout edit match.
-
-The full matrix currently exits nonzero because of the strict pixel threshold
-and the two behavior gaps.
-It does not prove every custom layout, live `serve` edit, remote font, or error
-policy combination. Configuration keys are represented across this matrix and
-the Python integration tests, but a finite set of cases cannot prove universal
-behavioral parity.
diff --git a/python/tests/fixtures/social/basic/docs/guide/advanced.md b/python/tests/fixtures/social/basic/docs/guide/advanced.md
deleted file mode 100644
index 931fae0..0000000
--- a/python/tests/fixtures/social/basic/docs/guide/advanced.md
+++ /dev/null
@@ -1 +0,0 @@
-# Advanced
diff --git a/python/tests/fixtures/social/basic/docs/guide/index.md b/python/tests/fixtures/social/basic/docs/guide/index.md
deleted file mode 100644
index 8c0d02f..0000000
--- a/python/tests/fixtures/social/basic/docs/guide/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Guide
diff --git a/python/tests/fixtures/social/basic/docs/index.md b/python/tests/fixtures/social/basic/docs/index.md
deleted file mode 100644
index 483e4cb..0000000
--- a/python/tests/fixtures/social/basic/docs/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Home & Intro
-description: A concise & useful description.
----
-
-# Welcome
diff --git a/python/tests/fixtures/social/basic/layouts/flat.yml b/python/tests/fixtures/social/basic/layouts/flat.yml
deleted file mode 100644
index ead527f..0000000
--- a/python/tests/fixtures/social/basic/layouts/flat.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-tags:
- og:type: website
- og:title: '{{ page.meta.get("title", page.title) }}'
- og:description: '{{ page.meta.get("description", config.site_description) | x }}'
- og:image: '{{ image.url }}'
- og:image:width: '{{ image.width }}'
- og:image:height: '{{ image.height }}'
- og:url: '{{ page.canonical_url }}'
- twitter:card: summary_large_image
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#123456' }
diff --git a/python/tests/fixtures/social/basic/mkdocs.yml b/python/tests/fixtures/social/basic/mkdocs.yml
deleted file mode 100644
index fca939e..0000000
--- a/python/tests/fixtures/social/basic/mkdocs.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-site_name: Social parity
-site_description: A small parity site.
-site_url: https://example.test/docs/
-site_dir: site
-theme:
- name: material
-plugins:
- - social:
- cache: false
- cards_layout: flat
diff --git a/python/tests/fixtures/social/blog/docs/blog/index.md b/python/tests/fixtures/social/blog/docs/blog/index.md
deleted file mode 100644
index 3d84c38..0000000
--- a/python/tests/fixtures/social/blog/docs/blog/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Journal
diff --git a/python/tests/fixtures/social/blog/docs/blog/posts/first.md b/python/tests/fixtures/social/blog/docs/blog/posts/first.md
deleted file mode 100644
index 6441b62..0000000
--- a/python/tests/fixtures/social/blog/docs/blog/posts/first.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-date: 2026-09-20
----
-
-# First post
diff --git a/python/tests/fixtures/social/blog/docs/blog/posts/second.md b/python/tests/fixtures/social/blog/docs/blog/posts/second.md
deleted file mode 100644
index dddb7d3..0000000
--- a/python/tests/fixtures/social/blog/docs/blog/posts/second.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-date: 2026-09-21
----
-
-# Second post
diff --git a/python/tests/fixtures/social/blog/docs/index.md b/python/tests/fixtures/social/blog/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/blog/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/blog/layouts/flat.yml b/python/tests/fixtures/social/blog/layouts/flat.yml
deleted file mode 100644
index 1c2dfa9..0000000
--- a/python/tests/fixtures/social/blog/layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:title: '{{ page.meta.get("title", page.title) }}'
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#123456' }
diff --git a/python/tests/fixtures/social/blog/mkdocs.yml b/python/tests/fixtures/social/blog/mkdocs.yml
deleted file mode 100644
index 2e8d9ff..0000000
--- a/python/tests/fixtures/social/blog/mkdocs.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-site_name: Social blog
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - blog
- - social:
- cache: false
- cards_layout: flat
diff --git a/python/tests/fixtures/social/bundled/docs/accent.md b/python/tests/fixtures/social/bundled/docs/accent.md
deleted file mode 100644
index 54805de..0000000
--- a/python/tests/fixtures/social/bundled/docs/accent.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: An accent card
-social:
- cards_layout: default/accent
----
-
-# Accent
diff --git a/python/tests/fixtures/social/bundled/docs/index.md b/python/tests/fixtures/social/bundled/docs/index.md
deleted file mode 100644
index 230c171..0000000
--- a/python/tests/fixtures/social/bundled/docs/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: A thoughtful beginning
-description: A concise introduction to the project.
----
-
-# Home
diff --git a/python/tests/fixtures/social/bundled/docs/invert.md b/python/tests/fixtures/social/bundled/docs/invert.md
deleted file mode 100644
index 7dd6c69..0000000
--- a/python/tests/fixtures/social/bundled/docs/invert.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: An inverted card
-social:
- cards_layout: default/invert
----
-
-# Invert
diff --git a/python/tests/fixtures/social/bundled/docs/variant.md b/python/tests/fixtures/social/bundled/docs/variant.md
deleted file mode 100644
index b419b90..0000000
--- a/python/tests/fixtures/social/bundled/docs/variant.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: A variant card
-social:
- cards_layout: default/variant
----
-
-# Variant
diff --git a/python/tests/fixtures/social/bundled/mkdocs.yml b/python/tests/fixtures/social/bundled/mkdocs.yml
deleted file mode 100644
index 0f8057b..0000000
--- a/python/tests/fixtures/social/bundled/mkdocs.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-site_name: Bundled cards
-site_description: A site description for cards.
-site_url: https://example.test/docs/
-site_dir: site
-theme:
- name: material
- palette:
- primary: deep purple
- font:
- text: Roboto
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: default
- cards_layout_options:
- background_color: '#4c1d95'
- color: '#ffffff'
- font_family: Roboto
diff --git a/python/tests/fixtures/social/cards-off/docs/index.md b/python/tests/fixtures/social/cards-off/docs/index.md
deleted file mode 100644
index 1183e04..0000000
--- a/python/tests/fixtures/social/cards-off/docs/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Home
-
-Global card generation is disabled for this page.
diff --git a/python/tests/fixtures/social/cards-off/docs/opt-in.md b/python/tests/fixtures/social/cards-off/docs/opt-in.md
deleted file mode 100644
index c4962d2..0000000
--- a/python/tests/fixtures/social/cards-off/docs/opt-in.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-social:
- cards: true
----
-
-# Page opt-in
-
-This page explicitly enables a card.
diff --git a/python/tests/fixtures/social/cards-off/layouts/flat.yml b/python/tests/fixtures/social/cards-off/layouts/flat.yml
deleted file mode 100644
index 27e1121..0000000
--- a/python/tests/fixtures/social/cards-off/layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:mode: opt-in
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#234567' }
diff --git a/python/tests/fixtures/social/cards-off/mkdocs.yml b/python/tests/fixtures/social/cards-off/mkdocs.yml
deleted file mode 100644
index 22cebbe..0000000
--- a/python/tests/fixtures/social/cards-off/mkdocs.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-site_name: Global cards off
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards: false
- cards_layout: flat
diff --git a/python/tests/fixtures/social/custom-typography/docs/index.md b/python/tests/fixtures/social/custom-typography/docs/index.md
deleted file mode 100644
index 521eb45..0000000
--- a/python/tests/fixtures/social/custom-typography/docs/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Custom typography
-description: A short description to inspect bottom alignment.
----
-
-# Home
diff --git a/python/tests/fixtures/social/custom-typography/docs/long.md b/python/tests/fixtures/social/custom-typography/docs/long.md
deleted file mode 100644
index 31cf621..0000000
--- a/python/tests/fixtures/social/custom-typography/docs/long.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: A very long headline that should wrap over several lines before the type becomes too small
-description: A longer description which should fit into two lines and reveal whether end alignment and line spacing match.
----
-
-# Long typography
diff --git a/python/tests/fixtures/social/custom-typography/layouts/type.yml b/python/tests/fixtures/social/custom-typography/layouts/type.yml
deleted file mode 100644
index 694f014..0000000
--- a/python/tests/fixtures/social/custom-typography/layouts/type.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:title: '{{ page.title }}'
-size: { width: 600, height: 315 }
-layers:
- - background: { color: '#14263d' }
- - size: { width: 500, height: 110 }
- offset: { x: 50, y: 40 }
- typography:
- content: '{{ page.meta.get("title", page.title) }}'
- align: center
- overflow: shrink
- color: '#ffffff'
- line: { amount: 2, height: 1.25 }
- font: { family: Roboto, style: Bold }
- - size: { width: 500, height: 65 }
- offset: { x: 50, y: 220 }
- typography:
- content: '{{ page.meta.get("description", "") }}'
- align: end bottom
- overflow: truncate
- color: '#bdd4eb'
- line: { amount: 2, height: 1.1 }
- font: { family: Roboto, style: Regular }
diff --git a/python/tests/fixtures/social/custom-typography/mkdocs.yml b/python/tests/fixtures/social/custom-typography/mkdocs.yml
deleted file mode 100644
index dd32639..0000000
--- a/python/tests/fixtures/social/custom-typography/mkdocs.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-site_name: Custom typography
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: type
diff --git a/python/tests/fixtures/social/debug-no-grid/docs/index.md b/python/tests/fixtures/social/debug-no-grid/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/debug-no-grid/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/debug-no-grid/layouts/flat.yml b/python/tests/fixtures/social/debug-no-grid/layouts/flat.yml
deleted file mode 100644
index 126e784..0000000
--- a/python/tests/fixtures/social/debug-no-grid/layouts/flat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#324a62' }
diff --git a/python/tests/fixtures/social/debug-no-grid/mkdocs.yml b/python/tests/fixtures/social/debug-no-grid/mkdocs.yml
deleted file mode 100644
index 0e52e72..0000000
--- a/python/tests/fixtures/social/debug-no-grid/mkdocs.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-site_name: Debug without grid
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: flat
- debug: true
- debug_on_build: true
- debug_grid: false
- debug_color: blue
diff --git a/python/tests/fixtures/social/debug/docs/index.md b/python/tests/fixtures/social/debug/docs/index.md
deleted file mode 100644
index fe1e66a..0000000
--- a/python/tests/fixtures/social/debug/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Debug view
diff --git a/python/tests/fixtures/social/debug/layouts/flat.yml b/python/tests/fixtures/social/debug/layouts/flat.yml
deleted file mode 100644
index b4548cd..0000000
--- a/python/tests/fixtures/social/debug/layouts/flat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#123456' }
diff --git a/python/tests/fixtures/social/debug/mkdocs.yml b/python/tests/fixtures/social/debug/mkdocs.yml
deleted file mode 100644
index 28de5e2..0000000
--- a/python/tests/fixtures/social/debug/mkdocs.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-site_name: Debug cards
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: flat
- debug: true
- debug_on_build: true
- debug_grid: true
- debug_grid_step: 32
- debug_color: red
diff --git a/python/tests/fixtures/social/deprecated/docs/index.md b/python/tests/fixtures/social/deprecated/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/deprecated/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/deprecated/layouts/flat.yml b/python/tests/fixtures/social/deprecated/layouts/flat.yml
deleted file mode 100644
index ce92f66..0000000
--- a/python/tests/fixtures/social/deprecated/layouts/flat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#102030' }
diff --git a/python/tests/fixtures/social/deprecated/mkdocs.yml b/python/tests/fixtures/social/deprecated/mkdocs.yml
deleted file mode 100644
index dce23be..0000000
--- a/python/tests/fixtures/social/deprecated/mkdocs.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-site_name: Deprecated social options
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: flat
- cards_color: { fill: '#102030', text: '#ffffff' }
- cards_font: Roboto
diff --git a/python/tests/fixtures/social/disabled/docs/index.md b/python/tests/fixtures/social/disabled/docs/index.md
deleted file mode 100644
index c38ca8e..0000000
--- a/python/tests/fixtures/social/disabled/docs/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Home
-
-The disabled plugin must not generate a card or social metadata.
diff --git a/python/tests/fixtures/social/disabled/docs/opt-in.md b/python/tests/fixtures/social/disabled/docs/opt-in.md
deleted file mode 100644
index 8f5b781..0000000
--- a/python/tests/fixtures/social/disabled/docs/opt-in.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-social:
- cards: true
----
-
-# Page opt-in
-
-A page override must not re-enable a disabled plugin.
diff --git a/python/tests/fixtures/social/disabled/mkdocs.yml b/python/tests/fixtures/social/disabled/mkdocs.yml
deleted file mode 100644
index 65cb8c1..0000000
--- a/python/tests/fixtures/social/disabled/mkdocs.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-site_name: Disabled social plugin
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- enabled: false
diff --git a/python/tests/fixtures/social/filters/docs/guides/hidden.md b/python/tests/fixtures/social/filters/docs/guides/hidden.md
deleted file mode 100644
index bb943c8..0000000
--- a/python/tests/fixtures/social/filters/docs/guides/hidden.md
+++ /dev/null
@@ -1 +0,0 @@
-# Include wins over exclude
diff --git a/python/tests/fixtures/social/filters/docs/guides/index.md b/python/tests/fixtures/social/filters/docs/guides/index.md
deleted file mode 100644
index d1a2daa..0000000
--- a/python/tests/fixtures/social/filters/docs/guides/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Included guide
diff --git a/python/tests/fixtures/social/filters/docs/guides/opt-out.md b/python/tests/fixtures/social/filters/docs/guides/opt-out.md
deleted file mode 100644
index 16b5394..0000000
--- a/python/tests/fixtures/social/filters/docs/guides/opt-out.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-social:
- cards: false
----
-
-# Page opt-out
diff --git a/python/tests/fixtures/social/filters/docs/index.md b/python/tests/fixtures/social/filters/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/filters/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/filters/layouts/flat.yml b/python/tests/fixtures/social/filters/layouts/flat.yml
deleted file mode 100644
index bb7c269..0000000
--- a/python/tests/fixtures/social/filters/layouts/flat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#abcdef' }
diff --git a/python/tests/fixtures/social/filters/mkdocs.yml b/python/tests/fixtures/social/filters/mkdocs.yml
deleted file mode 100644
index 570c667..0000000
--- a/python/tests/fixtures/social/filters/mkdocs.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-site_name: Social filters
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: flat
- cards_include: ['guides/**']
- cards_exclude: ['guides/hidden.md']
diff --git a/python/tests/fixtures/social/image-only/docs/background.svg b/python/tests/fixtures/social/image-only/docs/background.svg
deleted file mode 100644
index e83454d..0000000
--- a/python/tests/fixtures/social/image-only/docs/background.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/python/tests/fixtures/social/image-only/docs/index.md b/python/tests/fixtures/social/image-only/docs/index.md
deleted file mode 100644
index 98e600b..0000000
--- a/python/tests/fixtures/social/image-only/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Image card
diff --git a/python/tests/fixtures/social/image-only/mkdocs.yml b/python/tests/fixtures/social/image-only/mkdocs.yml
deleted file mode 100644
index 3758595..0000000
--- a/python/tests/fixtures/social/image-only/mkdocs.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-site_name: Social image only
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: default/only/image
- cards_layout_options:
- background_image: docs/background.svg
diff --git a/python/tests/fixtures/social/layer-composition/docs/background.svg b/python/tests/fixtures/social/layer-composition/docs/background.svg
deleted file mode 100644
index a77af77..0000000
--- a/python/tests/fixtures/social/layer-composition/docs/background.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/python/tests/fixtures/social/layer-composition/docs/index.md b/python/tests/fixtures/social/layer-composition/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/layer-composition/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/layer-composition/layouts/composed.yml b/python/tests/fixtures/social/layer-composition/layouts/composed.yml
deleted file mode 100644
index ea42e7b..0000000
--- a/python/tests/fixtures/social/layer-composition/layouts/composed.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-definitions:
- - &accent '#eeaa66'
-tags:
- og:image: '{{ image.url }}'
- x:layer: composed
-size: { width: 400, height: 200 }
-layers:
- - background: { image: docs/background.svg }
- - size: { width: 100, height: 40 }
- offset: { x: 20, y: 20 }
- background: { color: *accent }
- - size: { width: 80, height: 80 }
- offset: { x: 16, y: 16 }
- origin: end bottom
- icon: { value: material/star, color: '#ffffff' }
diff --git a/python/tests/fixtures/social/layer-composition/mkdocs.yml b/python/tests/fixtures/social/layer-composition/mkdocs.yml
deleted file mode 100644
index 648b646..0000000
--- a/python/tests/fixtures/social/layer-composition/mkdocs.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-site_name: Layer composition
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: composed
diff --git a/python/tests/fixtures/social/layout-options/docs/background.svg b/python/tests/fixtures/social/layout-options/docs/background.svg
deleted file mode 100644
index 2ed02a5..0000000
--- a/python/tests/fixtures/social/layout-options/docs/background.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/python/tests/fixtures/social/layout-options/docs/index.md b/python/tests/fixtures/social/layout-options/docs/index.md
deleted file mode 100644
index 15953ba..0000000
--- a/python/tests/fixtures/social/layout-options/docs/index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Home title
-description: Home description.
----
-
-# Home
-
-The global layout options supply the visible title and description.
diff --git a/python/tests/fixtures/social/layout-options/docs/logo.svg b/python/tests/fixtures/social/layout-options/docs/logo.svg
deleted file mode 100644
index 4f73dfe..0000000
--- a/python/tests/fixtures/social/layout-options/docs/logo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/python/tests/fixtures/social/layout-options/docs/override.md b/python/tests/fixtures/social/layout-options/docs/override.md
deleted file mode 100644
index 29c4989..0000000
--- a/python/tests/fixtures/social/layout-options/docs/override.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: Page title
-description: Page description.
-social:
- cards_layout_options:
- background_image: null
- color: '#ffe0a3'
- logo: null
- title: Page card title
- description: Page card description
----
-
-# Override
-
-This page merges its layout options with the global settings.
diff --git a/python/tests/fixtures/social/layout-options/mkdocs.yml b/python/tests/fixtures/social/layout-options/mkdocs.yml
deleted file mode 100644
index bac1a9d..0000000
--- a/python/tests/fixtures/social/layout-options/mkdocs.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-site_name: Layout options
-site_description: Default card description.
-site_url: https://example.test/docs/
-site_dir: site
-theme:
- name: material
- palette:
- primary: teal
- font:
- text: Roboto
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: default
- cards_layout_options:
- background_color: '#2d4059'
- background_image: docs/background.svg
- color: '#ffffff'
- font_family: Roboto
- logo: docs/logo.svg
- title: Global title
- description: Global description
diff --git a/python/tests/fixtures/social/lifecycle/docs/background.svg b/python/tests/fixtures/social/lifecycle/docs/background.svg
deleted file mode 100644
index 465bc52..0000000
--- a/python/tests/fixtures/social/lifecycle/docs/background.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/python/tests/fixtures/social/lifecycle/docs/index.md b/python/tests/fixtures/social/lifecycle/docs/index.md
deleted file mode 100644
index 34fc413..0000000
--- a/python/tests/fixtures/social/lifecycle/docs/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Before title
----
-
-# Home
diff --git a/python/tests/fixtures/social/lifecycle/layouts/flat.yml b/python/tests/fixtures/social/lifecycle/layouts/flat.yml
deleted file mode 100644
index 18c4ee3..0000000
--- a/python/tests/fixtures/social/lifecycle/layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:title: '{{ page.meta.get("title", page.title) }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { image: docs/background.svg, color: transparent }
diff --git a/python/tests/fixtures/social/lifecycle/mkdocs.yml b/python/tests/fixtures/social/lifecycle/mkdocs.yml
deleted file mode 100644
index d789b8f..0000000
--- a/python/tests/fixtures/social/lifecycle/mkdocs.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-site_name: Cached social lifecycle
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: true
- cache_dir: social-cache
- cards_layout: flat
diff --git a/python/tests/fixtures/social/lifecycle/steps.json b/python/tests/fixtures/social/lifecycle/steps.json
deleted file mode 100644
index 5b99823..0000000
--- a/python/tests/fixtures/social/lifecycle/steps.json
+++ /dev/null
@@ -1,20 +0,0 @@
-[
- {
- "path": "docs/background.svg",
- "before": "fill=\"red\"",
- "after": "fill=\"blue\"",
- "changes": ["cards"]
- },
- {
- "path": "docs/index.md",
- "before": "Before title",
- "after": "After title",
- "changes": ["pages"]
- },
- {
- "path": "layouts/flat.yml",
- "before": "color: transparent",
- "after": "color: '#73628a'",
- "changes": ["cards"]
- }
-]
diff --git a/python/tests/fixtures/social/logo-icon/docs/index.md b/python/tests/fixtures/social/logo-icon/docs/index.md
deleted file mode 100644
index 3552d38..0000000
--- a/python/tests/fixtures/social/logo-icon/docs/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: A bright idea
-description: A card with a logo icon.
----
-
-# Home
diff --git a/python/tests/fixtures/social/logo-icon/mkdocs.yml b/python/tests/fixtures/social/logo-icon/mkdocs.yml
deleted file mode 100644
index b33df16..0000000
--- a/python/tests/fixtures/social/logo-icon/mkdocs.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-site_name: Logo icon cards
-site_url: https://example.test/docs/
-site_dir: site
-theme:
- name: material
- icon:
- logo: material/star
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
diff --git a/python/tests/fixtures/social/metadata/docs/guides/.meta.yml b/python/tests/fixtures/social/metadata/docs/guides/.meta.yml
deleted file mode 100644
index 01ff2f7..0000000
--- a/python/tests/fixtures/social/metadata/docs/guides/.meta.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-social:
- cards_layout_options:
- background_color: '#445566'
- label: inherited
diff --git a/python/tests/fixtures/social/metadata/docs/guides/index.md b/python/tests/fixtures/social/metadata/docs/guides/index.md
deleted file mode 100644
index b0f52a5..0000000
--- a/python/tests/fixtures/social/metadata/docs/guides/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Inherited options
diff --git a/python/tests/fixtures/social/metadata/docs/guides/override.md b/python/tests/fixtures/social/metadata/docs/guides/override.md
deleted file mode 100644
index 68687f3..0000000
--- a/python/tests/fixtures/social/metadata/docs/guides/override.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-social:
- cards_layout_options:
- background_color: '#778899'
- label: page
----
-
-# Page options
diff --git a/python/tests/fixtures/social/metadata/docs/index.md b/python/tests/fixtures/social/metadata/docs/index.md
deleted file mode 100644
index f8d1ea4..0000000
--- a/python/tests/fixtures/social/metadata/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Global options
diff --git a/python/tests/fixtures/social/metadata/layouts/flat.yml b/python/tests/fixtures/social/metadata/layouts/flat.yml
deleted file mode 100644
index 828d5cd..0000000
--- a/python/tests/fixtures/social/metadata/layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:label: '{{ layout.label }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '{{ layout.background_color }}' }
diff --git a/python/tests/fixtures/social/metadata/mkdocs.yml b/python/tests/fixtures/social/metadata/mkdocs.yml
deleted file mode 100644
index c6b336e..0000000
--- a/python/tests/fixtures/social/metadata/mkdocs.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-site_name: Social metadata
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - meta
- - social:
- cache: false
- cards_layout: flat
- cards_layout_options:
- background_color: '#112233'
- label: global
diff --git a/python/tests/fixtures/social/multiple/docs/guide.md b/python/tests/fixtures/social/multiple/docs/guide.md
deleted file mode 100644
index 8c0d02f..0000000
--- a/python/tests/fixtures/social/multiple/docs/guide.md
+++ /dev/null
@@ -1 +0,0 @@
-# Guide
diff --git a/python/tests/fixtures/social/multiple/docs/index.md b/python/tests/fixtures/social/multiple/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/multiple/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/multiple/layouts/flat.yml b/python/tests/fixtures/social/multiple/layouts/flat.yml
deleted file mode 100644
index 8e35098..0000000
--- a/python/tests/fixtures/social/multiple/layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:instance: '{{ layout.label }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '{{ layout.color }}' }
diff --git a/python/tests/fixtures/social/multiple/mkdocs.yml b/python/tests/fixtures/social/multiple/mkdocs.yml
deleted file mode 100644
index fb0170a..0000000
--- a/python/tests/fixtures/social/multiple/mkdocs.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-site_name: Social instances
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: flat
- cards_dir: assets/cards/first
- cards_include: ['index.md']
- cards_layout_options: { label: first, color: '#102030' }
- - social:
- cache: false
- cards_layout: flat
- cards_dir: assets/cards/second
- cards_include: ['guide.md']
- cards_layout_options: { label: second, color: '#405060' }
diff --git a/python/tests/fixtures/social/no-site-url/docs/index.md b/python/tests/fixtures/social/no-site-url/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/no-site-url/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/no-site-url/layouts/flat.yml b/python/tests/fixtures/social/no-site-url/layouts/flat.yml
deleted file mode 100644
index b4548cd..0000000
--- a/python/tests/fixtures/social/no-site-url/layouts/flat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#123456' }
diff --git a/python/tests/fixtures/social/no-site-url/mkdocs.yml b/python/tests/fixtures/social/no-site-url/mkdocs.yml
deleted file mode 100644
index 6416702..0000000
--- a/python/tests/fixtures/social/no-site-url/mkdocs.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-site_name: Social without URL
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: flat
diff --git a/python/tests/fixtures/social/paths/card-layouts/flat.yml b/python/tests/fixtures/social/paths/card-layouts/flat.yml
deleted file mode 100644
index 05057bb..0000000
--- a/python/tests/fixtures/social/paths/card-layouts/flat.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- og:url: '{{ page.canonical_url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#486284' }
diff --git a/python/tests/fixtures/social/paths/docs/guides.md b/python/tests/fixtures/social/paths/docs/guides.md
deleted file mode 100644
index 50a8673..0000000
--- a/python/tests/fixtures/social/paths/docs/guides.md
+++ /dev/null
@@ -1 +0,0 @@
-# Guides
diff --git a/python/tests/fixtures/social/paths/docs/index.md b/python/tests/fixtures/social/paths/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/paths/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/paths/docs/nested/index.md b/python/tests/fixtures/social/paths/docs/nested/index.md
deleted file mode 100644
index 53eb92e..0000000
--- a/python/tests/fixtures/social/paths/docs/nested/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Nested index
diff --git a/python/tests/fixtures/social/paths/mkdocs.yml b/python/tests/fixtures/social/paths/mkdocs.yml
deleted file mode 100644
index aceb359..0000000
--- a/python/tests/fixtures/social/paths/mkdocs.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-site_name: Flat URL paths
-site_url: https://example.test/docs/
-site_dir: site
-use_directory_urls: false
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_dir: assets/cards
- cards_layout_dir: card-layouts
- cards_layout: flat
diff --git a/python/tests/fixtures/social/recoverable-error/docs/index.md b/python/tests/fixtures/social/recoverable-error/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/recoverable-error/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/recoverable-error/layouts/missing-image.yml b/python/tests/fixtures/social/recoverable-error/layouts/missing-image.yml
deleted file mode 100644
index 30d1025..0000000
--- a/python/tests/fixtures/social/recoverable-error/layouts/missing-image.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { image: docs/does-not-exist.svg }
diff --git a/python/tests/fixtures/social/recoverable-error/mkdocs.yml b/python/tests/fixtures/social/recoverable-error/mkdocs.yml
deleted file mode 100644
index d77eb9a..0000000
--- a/python/tests/fixtures/social/recoverable-error/mkdocs.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-site_name: Recoverable social error
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: missing-image
- log: true
- log_level: ignore
diff --git a/python/tests/fixtures/social/template-context/docs/guides/advanced.md b/python/tests/fixtures/social/template-context/docs/guides/advanced.md
deleted file mode 100644
index 931fae0..0000000
--- a/python/tests/fixtures/social/template-context/docs/guides/advanced.md
+++ /dev/null
@@ -1 +0,0 @@
-# Advanced
diff --git a/python/tests/fixtures/social/template-context/docs/index.md b/python/tests/fixtures/social/template-context/docs/index.md
deleted file mode 100644
index 291ca38..0000000
--- a/python/tests/fixtures/social/template-context/docs/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Home
diff --git a/python/tests/fixtures/social/template-context/layouts/context.yml b/python/tests/fixtures/social/template-context/layouts/context.yml
deleted file mode 100644
index cfa8df0..0000000
--- a/python/tests/fixtures/social/template-context/layouts/context.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
- x:source: '{{ page.file.src_uri }}'
- x:page: '{{ page.url }}'
-size: { width: 320, height: 168 }
-layers:
- - background: { color: '#357192' }
diff --git a/python/tests/fixtures/social/template-context/mkdocs.yml b/python/tests/fixtures/social/template-context/mkdocs.yml
deleted file mode 100644
index 0de90ac..0000000
--- a/python/tests/fixtures/social/template-context/mkdocs.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-site_name: Social template context
-site_url: https://example.test/docs/
-site_dir: site
-theme: { name: material }
-plugins:
- - social:
- cache: false
- cards_layout: context
diff --git a/python/tests/fixtures/social/theme-defaults/docs/assets/logo.png b/python/tests/fixtures/social/theme-defaults/docs/assets/logo.png
deleted file mode 100644
index 5743354..0000000
Binary files a/python/tests/fixtures/social/theme-defaults/docs/assets/logo.png and /dev/null differ
diff --git a/python/tests/fixtures/social/theme-defaults/docs/assets/logo.svg b/python/tests/fixtures/social/theme-defaults/docs/assets/logo.svg
deleted file mode 100644
index 427ba78..0000000
--- a/python/tests/fixtures/social/theme-defaults/docs/assets/logo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/python/tests/fixtures/social/theme-defaults/docs/exact.md b/python/tests/fixtures/social/theme-defaults/docs/exact.md
deleted file mode 100644
index 8e01a8f..0000000
--- a/python/tests/fixtures/social/theme-defaults/docs/exact.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Italic
-social:
- cards_layout: exact
----
-
-# Exact font face
diff --git a/python/tests/fixtures/social/theme-defaults/docs/index.md b/python/tests/fixtures/social/theme-defaults/docs/index.md
deleted file mode 100644
index 8e1099b..0000000
--- a/python/tests/fixtures/social/theme-defaults/docs/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Theme colors
-description: Card colors and logo come from the theme.
----
-
-# Home
diff --git a/python/tests/fixtures/social/theme-defaults/docs/png.md b/python/tests/fixtures/social/theme-defaults/docs/png.md
deleted file mode 100644
index cb9e223..0000000
--- a/python/tests/fixtures/social/theme-defaults/docs/png.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: PNG logo
-description: This page uses a raster logo through its layout options.
-social:
- cards_layout_options:
- logo: docs/assets/logo.png
----
-
-# PNG logo
diff --git a/python/tests/fixtures/social/theme-defaults/docs/variant.md b/python/tests/fixtures/social/theme-defaults/docs/variant.md
deleted file mode 100644
index 087fdbe..0000000
--- a/python/tests/fixtures/social/theme-defaults/docs/variant.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: Heavier font variant
-description: This page selects the Black variant of Roboto.
-social:
- cards_layout_options:
- font_variant: Black
----
-
-# Variant
diff --git a/python/tests/fixtures/social/theme-defaults/layouts/exact.yml b/python/tests/fixtures/social/theme-defaults/layouts/exact.yml
deleted file mode 100644
index f66f210..0000000
--- a/python/tests/fixtures/social/theme-defaults/layouts/exact.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-tags:
- og:image: '{{ image.url }}'
-size: { width: 1200, height: 630 }
-layers:
- - background: { color: '#ffc107' }
- - size: { width: 900, height: 150 }
- offset: { x: 64, y: 180 }
- typography:
- content: '{{ page.title }}'
- color: '#000000'
- line: { amount: 1, height: 1.25 }
- font: { family: Roboto, variant: Black, style: Italic }
diff --git a/python/tests/fixtures/social/theme-defaults/mkdocs.yml b/python/tests/fixtures/social/theme-defaults/mkdocs.yml
deleted file mode 100644
index 1dbff58..0000000
--- a/python/tests/fixtures/social/theme-defaults/mkdocs.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-site_name: Theme defaults
-site_description: Theme-derived card settings.
-site_url: https://example.test/docs/
-site_dir: site
-theme:
- name: material
- logo: assets/logo.svg
- palette:
- - media: '(prefers-color-scheme: light)'
- primary: amber
- accent: deep orange
- - media: '(prefers-color-scheme: dark)'
- primary: indigo
- accent: pink
- font:
- text: Roboto
-plugins:
- - social:
- cache: false
- cache_dir: social-cache
- cards_layout: default
diff --git a/scripts/social_compatibility.py b/scripts/social_compatibility.py
deleted file mode 100755
index fcdf005..0000000
--- a/scripts/social_compatibility.py
+++ /dev/null
@@ -1,283 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2025-2026 Zensical and contributors
-
-# SPDX-License-Identifier: MIT
-# All contributions are certified under the DCO
-
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-# sell copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-# IN THE SOFTWARE.
-
-"""Compare generated Material and Zensical social cards and metadata."""
-
-from __future__ import annotations
-
-import argparse
-import hashlib
-import json
-import shutil
-import subprocess
-import tempfile
-from pathlib import Path
-
-from bs4 import BeautifulSoup
-from PIL import Image, ImageChops, ImageStat
-
-ROOT = Path(__file__).resolve().parents[1]
-FIXTURES = ROOT / "python" / "tests" / "fixtures" / "social"
-
-
-def arguments() -> argparse.Namespace:
- """Parse builder paths and optional cases."""
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument("--mkdocs", type=Path, required=True)
- parser.add_argument("--zensical", type=Path, required=True)
- parser.add_argument("--case", action="append", dest="cases")
- parser.add_argument(
- "--output", type=Path, help="keep builds in this empty directory"
- )
- parser.add_argument(
- "--font-cache", type=Path, help="existing social font cache"
- )
- return parser.parse_args()
-
-
-def build(executable: Path, project: Path, *, strict: bool) -> None:
- """Build one copied fixture in its own project directory."""
- command = [
- str(executable),
- "build",
- "--clean",
- "--config-file",
- "mkdocs.yml",
- ]
- if strict:
- command.append("--strict")
- result = subprocess.run(
- command, cwd=project, capture_output=True, text=True, check=False
- )
- (project / "build.log").write_text(
- f"$ {' '.join(command)}\n{result.stdout}{result.stderr}",
- encoding="utf-8",
- )
- if result.returncode:
- details = (result.stderr or result.stdout).strip().splitlines()
- summary = details[-1] if details else "no diagnostics"
- raise RuntimeError(
- f"{executable.name} build failed in {project} "
- f"({result.returncode}): {summary}"
- )
-
-
-def manifest(site: Path) -> dict:
- """Extract only social metadata and generated image facts."""
- pages = {}
- for path in sorted(site.rglob("*.html")):
- if path.name == "404.html":
- continue
- soup = BeautifulSoup(path.read_text(encoding="utf-8"), "lxml")
- if soup.head is None:
- raise ValueError(f"generated page lacks a head element: {path}")
- tags = [
- [property_name, tag.get("content")]
- for tag in soup.head.find_all("meta", property=True)
- if isinstance(property_name := tag.get("property"), str)
- and property_name.startswith(("og:", "twitter:", "x:"))
- ]
- pages[path.relative_to(site).as_posix()] = tags
-
- cards = {}
- for path in sorted(site.rglob("*.png")):
- relative = path.relative_to(site).as_posix()
- if not relative.startswith(("assets/images/social/", "assets/cards/")):
- continue
- with Image.open(path) as image:
- cards[relative] = list(image.size)
- return {"pages": pages, "cards": cards}
-
-
-def image_difference(first: Path, second: Path) -> float:
- """Measure mean RGB channel difference independent of PNG encoding."""
- with Image.open(first) as left, Image.open(second) as right:
- if left.size != right.size:
- return float("inf")
- difference = ImageChops.difference(
- left.convert("RGB"), right.convert("RGB")
- )
- return sum(ImageStat.Stat(difference).mean) / 3
-
-
-def compare(name: str, material: Path, zensical: Path) -> bool:
- """Show semantic and visual differences for one fixture."""
- expected = manifest(material / "site")
- actual = manifest(zensical / "site")
- (material / "manifest.json").write_text(
- json.dumps(expected, indent=2, ensure_ascii=False) + "\n",
- encoding="utf-8",
- )
- (zensical / "manifest.json").write_text(
- json.dumps(actual, indent=2, ensure_ascii=False) + "\n",
- encoding="utf-8",
- )
- cards = set(expected["cards"]) | set(actual["cards"])
- differences = {
- path: round(
- image_difference(
- material / "site" / path, zensical / "site" / path
- ),
- 3,
- )
- for path in sorted(set(expected["cards"]) & set(actual["cards"]))
- }
- matched = expected == actual and all(
- value <= 1 for value in differences.values()
- )
- print(
- f"{name}: {'MATCH' if matched else 'DIFF'}; "
- f"{len(expected['pages'])} pages, {len(cards)} card paths; "
- f"mean RGB error {max(differences.values(), default=0):.3f}"
- )
- if expected != actual:
- for key in ("pages", "cards"):
- for path in sorted(set(expected[key]) | set(actual[key])):
- if expected[key].get(path) != actual[key].get(path):
- print(f" {key}/{path}")
- print(f" Material: {expected[key].get(path)}")
- print(f" Zensical: {actual[key].get(path)}")
- for path, error in differences.items():
- if error > 1:
- print(f" pixels/{path}: mean RGB error {error:.3f}")
- return matched
-
-
-def snapshot(project: Path) -> dict:
- """Capture visible metadata and card contents between cached builds."""
- site = project / "site"
- current = manifest(site)
- return {
- "pages": current["pages"],
- "cards": {
- path: hashlib.sha256((site / path).read_bytes()).hexdigest()
- for path in current["cards"]
- },
- }
-
-
-def apply_step(project: Path, step: dict) -> None:
- """Apply one exact source replacement in a copied fixture."""
- path = project / step["path"]
- source = path.read_text(encoding="utf-8")
- before = step["before"]
- if source.count(before) != 1:
- raise ValueError(f"expected one occurrence of {before!r} in {path}")
- path.write_text(source.replace(before, step["after"]), encoding="utf-8")
-
-
-def run(root: Path, args: argparse.Namespace) -> bool:
- """Build selected fixtures in both engines and compare their outputs."""
- names = args.cases or sorted(
- path.name
- for path in FIXTURES.iterdir()
- if (path / "mkdocs.yml").is_file()
- )
- unknown = [
- name for name in names if not (FIXTURES / name / "mkdocs.yml").is_file()
- ]
- if unknown:
- raise ValueError(f"unknown social cases: {', '.join(unknown)}")
- matched = True
- for name in names:
- project = FIXTURES / name
- material = root / name / "material"
- zensical = root / name / "zensical"
- shutil.copytree(project, material)
- shutil.copytree(project, zensical)
- if args.font_cache and name in {
- "bundled",
- "custom-typography",
- "debug",
- "debug-no-grid",
- "layout-options",
- "logo-icon",
- "theme-defaults",
- }:
- for destination in (material, zensical):
- shutil.copytree(
- args.font_cache,
- destination / "social-cache/fonts",
- )
- strict = name not in {
- "debug",
- "debug-no-grid",
- "deprecated",
- "no-site-url",
- }
- try:
- build(args.mkdocs, material, strict=strict)
- build(args.zensical, zensical, strict=strict)
- matched &= compare(name, material, zensical)
- steps_file = project / "steps.json"
- if not steps_file.is_file():
- continue
- for index, step in enumerate(json.loads(steps_file.read_text())):
- previous = {
- "material": snapshot(material),
- "zensical": snapshot(zensical),
- }
- for destination in (material, zensical):
- apply_step(destination, step)
- build(args.mkdocs, material, strict=strict)
- build(args.zensical, zensical, strict=strict)
- label = f"{name}/step-{index + 1}"
- matched &= compare(label, material, zensical)
- for engine, destination in (
- ("material", material),
- ("zensical", zensical),
- ):
- current = snapshot(destination)
- for kind in step["changes"]:
- if previous[engine][kind] == current[kind]:
- print(f"{label}: {engine} {kind} did not change")
- matched = False
- except RuntimeError as error:
- print(f"{name}: ERROR; {error}")
- matched = False
- continue
- return matched
-
-
-def main() -> int:
- """Run the selected compatibility matrix."""
- args = arguments()
- args.mkdocs = args.mkdocs.resolve()
- args.zensical = args.zensical.resolve()
- if args.font_cache:
- args.font_cache = args.font_cache.resolve()
- if args.output:
- root = args.output.resolve()
- root.mkdir(parents=True, exist_ok=True)
- if any(root.iterdir()):
- raise ValueError(f"output directory must be empty: {root}")
- print(f"Builds: {root}")
- return 0 if run(root, args) else 1
- with tempfile.TemporaryDirectory(prefix="zensical-social-parity-") as raw:
- return 0 if run(Path(raw), args) else 1
-
-
-if __name__ == "__main__":
- raise SystemExit(main())