diff --git a/docs/explorations/.fallow-baseline.json b/docs/explorations/.fallow-baseline.json
index c3d5662f9..dddf65bfc 100644
--- a/docs/explorations/.fallow-baseline.json
+++ b/docs/explorations/.fallow-baseline.json
@@ -1,3 +1,3 @@
{
- "count": 41
+ "count": 50
}
diff --git a/docs/explorations/STALE.md b/docs/explorations/STALE.md
index a94c8a3bd..1beca5fae 100644
--- a/docs/explorations/STALE.md
+++ b/docs/explorations/STALE.md
@@ -14,7 +14,7 @@ review: 2027-02-01 # renew the claim
status: withdrawn # release it; the document stays exactly where it is
```
-**41** stale of 310 undecided.
+**50** stale of 310 undecided.
## How this backlog retires
@@ -25,15 +25,15 @@ cannot drag the curve down.
| Days since written | Cohort | Still unshipped |
| --- | --- | --- |
| 1 | 448 | 57% |
-| 7 | 445 | 56% |
+| 7 | 446 | 56% |
| 14 | 445 | 56% |
| 30 | 444 | 55% |
-| 60 | 428 | 54% |
-| 90 | 280 | 50% |
-| 120 | 156 | 62% |
+| 60 | 431 | 54% |
+| 90 | 295 | 49% |
+| 120 | 165 | 60% |
The curve does not fall: 57% of documents at least a day old are
-unshipped, and 62% at 120 days. An exploration is checked off
+unshipped, and 60% at 120 days. An exploration is checked off
within days of being written, or never — so an old `[_]` is not a pending
decision, it is a decision already made by inaction. Renew it deliberately,
or withdraw it; both are one line and neither renames the file.
@@ -42,47 +42,56 @@ or withdraw it; both are one line and neither renames the file.
| Exploration | Due | Overdue | Decider |
| --- | --- | --- | --- |
-| [0079_[_]_AUTH_SCHEMA_DSL_VARIATIONS.md](0079_%5B_%5D_AUTH_SCHEMA_DSL_VARIATIONS.md) | 2026-05-09 *(default)* | 150d | — |
-| [0080_[_]_UCAN_HYBRID_AUTHORIZATION_INTEGRATION.md](0080_%5B_%5D_UCAN_HYBRID_AUTHORIZATION_INTEGRATION.md) | 2026-05-10 *(default)* | 149d | — |
-| [0081_[_]_NODE_PERMISSIONS_UCAN_EVALUATION.md](0081_%5B_%5D_NODE_PERMISSIONS_UCAN_EVALUATION.md) | 2026-05-10 *(default)* | 149d | — |
-| [0082_[_]_GLOBAL_NAMESPACE_AUTHORIZATION.md](0082_%5B_%5D_GLOBAL_NAMESPACE_AUTHORIZATION.md) | 2026-05-10 *(default)* | 149d | — |
-| [0083_[_]_UNIFIED_AUTHORIZATION_ARCHITECTURE.md](0083_%5B_%5D_UNIFIED_AUTHORIZATION_ARCHITECTURE.md) | 2026-05-10 *(default)* | 149d | — |
-| [0084_[_]_GROUPS_AS_RELATIONS.md](0084_%5B_%5D_GROUPS_AS_RELATIONS.md) | 2026-05-10 *(default)* | 149d | — |
-| [0086_[_]_NATIVE_REWRITE_ZIG_RUST.md](0086_%5B_%5D_NATIVE_REWRITE_ZIG_RUST.md) | 2026-05-12 *(default)* | 147d | — |
-| [0088_[_]_DATABASE_UI_COMPETITIVE_ARCHITECTURE.md](0088_%5B_%5D_DATABASE_UI_COMPETITIVE_ARCHITECTURE.md) | 2026-05-13 *(default)* | 146d | — |
-| [0089_[_]_REST_GRAPHQL_INTEROPERABILITY_BOUNDARY.md](0089_%5B_%5D_REST_GRAPHQL_INTEROPERABILITY_BOUNDARY.md) | 2026-05-18 *(default)* | 141d | — |
-| [0090_[_]_ELECTRON_P2P_REMOTE_SHARE_OPTIONS.md](0090_%5B_%5D_ELECTRON_P2P_REMOTE_SHARE_OPTIONS.md) | 2026-05-21 *(default)* | 138d | — |
-| [0091_[_]_GLOBAL_SCHEMA_FEDERATION_MODEL.md](0091_%5B_%5D_GLOBAL_SCHEMA_FEDERATION_MODEL.md) | 2026-05-21 *(default)* | 138d | — |
-| [0093_[_]_NODE_NATIVE_GLOBAL_SCHEMA_FEDERATION_MODEL.md](0093_%5B_%5D_NODE_NATIVE_GLOBAL_SCHEMA_FEDERATION_MODEL.md) | 2026-05-21 *(default)* | 138d | — |
-| [0095_[_]_PACKAGE_PORTFOLIO_CLEANUP_AND_API_SIMPLIFICATION.md](0095_%5B_%5D_PACKAGE_PORTFOLIO_CLEANUP_AND_API_SIMPLIFICATION.md) | 2026-05-30 *(default)* | 129d | — |
-| [0096_[_]_PLAN03_ERP_REALITY_CHECK_AND_EXECUTION_RESET.md](0096_%5B_%5D_PLAN03_ERP_REALITY_CHECK_AND_EXECUTION_RESET.md) | 2026-05-30 *(default)* | 129d | — |
-| [0098_[_]_OPENCLAW_INTEGRATION.md](0098_%5B_%5D_OPENCLAW_INTEGRATION.md) | 2026-06-01 *(default)* | 127d | — |
-| [0099_[_]_DATABASE_EDITING_UX_AND_UNDO_REDO_REMEDIATION_PLAN.md](0099_%5B_%5D_DATABASE_EDITING_UX_AND_UNDO_REDO_REMEDIATION_PLAN.md) | 2026-06-01 *(default)* | 127d | — |
-| [0100_[_]_NPM_PUBLISH_WORKFLOW_FOR_XNETJS.md](0100_%5B_%5D_NPM_PUBLISH_WORKFLOW_FOR_XNETJS.md) | 2026-06-02 *(default)* | 126d | — |
-| [0101_[_]_END_TO_END_NPM_TRUSTED_PUBLISHING_PLAYBOOK.md](0101_%5B_%5D_END_TO_END_NPM_TRUSTED_PUBLISHING_PLAYBOOK.md) | 2026-06-03 *(default)* | 125d | — |
-| [0102_[_]_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md](0102_%5B_%5D_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md) | 2026-06-03 *(default)* | 125d | — |
-| [0103_[-]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md](0103_%5B-%5D_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md) | 2026-06-04 *(default)* | 124d | — |
-| [0104_[-]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md](0104_%5B-%5D_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md) | 2026-06-04 *(default)* | 124d | — |
-| [0105_[_]_WHAT_TO_WORK_ON_NEXT_AFTER_OPEN_SOURCE_LAUNCH.md](0105_%5B_%5D_WHAT_TO_WORK_ON_NEXT_AFTER_OPEN_SOURCE_LAUNCH.md) | 2026-06-05 *(default)* | 123d | — |
-| [0106_[_]_CI_PERF_TESTING_OPTIONS.md](0106_%5B_%5D_CI_PERF_TESTING_OPTIONS.md) | 2026-06-05 *(default)* | 123d | — |
-| [0106_[_]_JOIN_QUERIES_MULTI_TYPE_AGGREGATES_QUERY_PLANNING_API.md](0106_%5B_%5D_JOIN_QUERIES_MULTI_TYPE_AGGREGATES_QUERY_PLANNING_API.md) | 2026-06-05 *(default)* | 123d | — |
-| [0107_[_]_STORYBOOK_PERFORMANCE_PANEL_AND_ELECTRON_IDE_WORKSHOP.md](0107_%5B_%5D_STORYBOOK_PERFORMANCE_PANEL_AND_ELECTRON_IDE_WORKSHOP.md) | 2026-06-06 *(default)* | 122d | — |
-| [0108_[_]_CANVAS_V1_PAGES_DATABASES_AND_INFINITE_CANVAS_DEEP_DIVE.md](0108_%5B_%5D_CANVAS_V1_PAGES_DATABASES_AND_INFINITE_CANVAS_DEEP_DIVE.md) | 2026-06-07 *(default)* | 121d | — |
-| [0108_[_]_EXPO_APP_PARITY_WITH_ELECTRON_AND_WEB.md](0108_%5B_%5D_EXPO_APP_PARITY_WITH_ELECTRON_AND_WEB.md) | 2026-06-07 *(default)* | 121d | — |
-| [0108_[_]_TIMING_FOR_INTEGRATING_CHAT_AND_VIDEO_INTO_XNET_NOW_VS_LATER.md](0108_%5B_%5D_TIMING_FOR_INTEGRATING_CHAT_AND_VIDEO_INTO_XNET_NOW_VS_LATER.md) | 2026-06-07 *(default)* | 121d | — |
-| [0108_[_]_USEQUERY_UPGRADE_TIMING_AND_INTEGRATION_SEQUENCING.md](0108_%5B_%5D_USEQUERY_UPGRADE_TIMING_AND_INTEGRATION_SEQUENCING.md) | 2026-06-07 *(default)* | 121d | — |
-| [0109_[_]_REPOSITORY_AND_PROJECT_SUMMARY_FOR_NON_TECHNICAL_USERS.md](0109_%5B_%5D_REPOSITORY_AND_PROJECT_SUMMARY_FOR_NON_TECHNICAL_USERS.md) | 2026-06-08 *(default)* | 120d | — |
-| [0110_[_]_XNET_AS_A_VIABLE_WIKIPEDIA_ALTERNATIVE.md](0110_%5B_%5D_XNET_AS_A_VIABLE_WIKIPEDIA_ALTERNATIVE.md) | 2026-07-04 *(default)* | 94d | — |
-| [0111_[_]_UNIFIED_WORKBENCH_ARCHITECTURE_FOR_XNET.md](0111_%5B_%5D_UNIFIED_WORKBENCH_ARCHITECTURE_FOR_XNET.md) | 2026-07-04 *(default)* | 94d | — |
-| [0112_[_]_UNIVERSAL_CLIPPER_AND_AI_KNOWLEDGE_GRAPH_INGESTION.md](0112_%5B_%5D_UNIVERSAL_CLIPPER_AND_AI_KNOWLEDGE_GRAPH_INGESTION.md) | 2026-07-04 *(default)* | 94d | — |
-| [0113_[_]_OTHER_INTERNET_INFRASTRUCTURE_ROLES_FOR_XNET.md](0113_%5B_%5D_OTHER_INTERNET_INFRASTRUCTURE_ROLES_FOR_XNET.md) | 2026-07-04 *(default)* | 94d | — |
-| [0114_[_]_DECENTRALIZED_ALTERNATIVES_FOR_NON_XNET_INTERNET_LAYERS.md](0114_%5B_%5D_DECENTRALIZED_ALTERNATIVES_FOR_NON_XNET_INTERNET_LAYERS.md) | 2026-07-04 *(default)* | 94d | — |
-| [0115_[_]_ARCHITECTING_FULLY_DECENTRALIZED_GLOBAL_WEB_SEARCH.md](0115_%5B_%5D_ARCHITECTING_FULLY_DECENTRALIZED_GLOBAL_WEB_SEARCH.md) | 2026-07-06 *(default)* | 92d | — |
-| [0116_[_]_ARCHITECTING_DECENTRALIZED_TWITTER_X_ON_XNET.md](0116_%5B_%5D_ARCHITECTING_DECENTRALIZED_TWITTER_X_ON_XNET.md) | 2026-07-06 *(default)* | 92d | — |
-| [0117_[_]_ARCHITECTING_DECENTRALIZED_AI_ON_XNET.md](0117_%5B_%5D_ARCHITECTING_DECENTRALIZED_AI_ON_XNET.md) | 2026-07-06 *(default)* | 92d | — |
-| [0118_[_]_ARCHITECTING_A_DECENTRALIZED_OSS_FORGE_ON_XNET.md](0118_%5B_%5D_ARCHITECTING_A_DECENTRALIZED_OSS_FORGE_ON_XNET.md) | 2026-07-06 *(default)* | 92d | — |
-| [0119_[_]_XNET_AS_A_COMPELLING_WEB_AND_MOBILE_DEVELOPER_TOOL.md](0119_%5B_%5D_XNET_AS_A_COMPELLING_WEB_AND_MOBILE_DEVELOPER_TOOL.md) | 2026-07-06 *(default)* | 92d | — |
-| [0120_[_]_XNET_PACKAGE_SECURITY_AND_RELIABILITY_EXPLORATION.md](0120_%5B_%5D_XNET_PACKAGE_SECURITY_AND_RELIABILITY_EXPLORATION.md) | 2026-07-06 *(default)* | 92d | — |
+| [0079_[_]_AUTH_SCHEMA_DSL_VARIATIONS.md](0079_%5B_%5D_AUTH_SCHEMA_DSL_VARIATIONS.md) | 2026-05-09 *(default)* | 153d | — |
+| [0080_[_]_UCAN_HYBRID_AUTHORIZATION_INTEGRATION.md](0080_%5B_%5D_UCAN_HYBRID_AUTHORIZATION_INTEGRATION.md) | 2026-05-10 *(default)* | 152d | — |
+| [0081_[_]_NODE_PERMISSIONS_UCAN_EVALUATION.md](0081_%5B_%5D_NODE_PERMISSIONS_UCAN_EVALUATION.md) | 2026-05-10 *(default)* | 152d | — |
+| [0082_[_]_GLOBAL_NAMESPACE_AUTHORIZATION.md](0082_%5B_%5D_GLOBAL_NAMESPACE_AUTHORIZATION.md) | 2026-05-10 *(default)* | 152d | — |
+| [0083_[_]_UNIFIED_AUTHORIZATION_ARCHITECTURE.md](0083_%5B_%5D_UNIFIED_AUTHORIZATION_ARCHITECTURE.md) | 2026-05-10 *(default)* | 152d | — |
+| [0084_[_]_GROUPS_AS_RELATIONS.md](0084_%5B_%5D_GROUPS_AS_RELATIONS.md) | 2026-05-10 *(default)* | 152d | — |
+| [0086_[_]_NATIVE_REWRITE_ZIG_RUST.md](0086_%5B_%5D_NATIVE_REWRITE_ZIG_RUST.md) | 2026-05-12 *(default)* | 150d | — |
+| [0088_[_]_DATABASE_UI_COMPETITIVE_ARCHITECTURE.md](0088_%5B_%5D_DATABASE_UI_COMPETITIVE_ARCHITECTURE.md) | 2026-05-13 *(default)* | 149d | — |
+| [0089_[_]_REST_GRAPHQL_INTEROPERABILITY_BOUNDARY.md](0089_%5B_%5D_REST_GRAPHQL_INTEROPERABILITY_BOUNDARY.md) | 2026-05-18 *(default)* | 144d | — |
+| [0090_[_]_ELECTRON_P2P_REMOTE_SHARE_OPTIONS.md](0090_%5B_%5D_ELECTRON_P2P_REMOTE_SHARE_OPTIONS.md) | 2026-05-21 *(default)* | 141d | — |
+| [0091_[_]_GLOBAL_SCHEMA_FEDERATION_MODEL.md](0091_%5B_%5D_GLOBAL_SCHEMA_FEDERATION_MODEL.md) | 2026-05-21 *(default)* | 141d | — |
+| [0093_[_]_NODE_NATIVE_GLOBAL_SCHEMA_FEDERATION_MODEL.md](0093_%5B_%5D_NODE_NATIVE_GLOBAL_SCHEMA_FEDERATION_MODEL.md) | 2026-05-21 *(default)* | 141d | — |
+| [0095_[_]_PACKAGE_PORTFOLIO_CLEANUP_AND_API_SIMPLIFICATION.md](0095_%5B_%5D_PACKAGE_PORTFOLIO_CLEANUP_AND_API_SIMPLIFICATION.md) | 2026-05-30 *(default)* | 132d | — |
+| [0096_[_]_PLAN03_ERP_REALITY_CHECK_AND_EXECUTION_RESET.md](0096_%5B_%5D_PLAN03_ERP_REALITY_CHECK_AND_EXECUTION_RESET.md) | 2026-05-30 *(default)* | 132d | — |
+| [0098_[_]_OPENCLAW_INTEGRATION.md](0098_%5B_%5D_OPENCLAW_INTEGRATION.md) | 2026-06-01 *(default)* | 130d | — |
+| [0099_[_]_DATABASE_EDITING_UX_AND_UNDO_REDO_REMEDIATION_PLAN.md](0099_%5B_%5D_DATABASE_EDITING_UX_AND_UNDO_REDO_REMEDIATION_PLAN.md) | 2026-06-01 *(default)* | 130d | — |
+| [0100_[_]_NPM_PUBLISH_WORKFLOW_FOR_XNETJS.md](0100_%5B_%5D_NPM_PUBLISH_WORKFLOW_FOR_XNETJS.md) | 2026-06-02 *(default)* | 129d | — |
+| [0101_[_]_END_TO_END_NPM_TRUSTED_PUBLISHING_PLAYBOOK.md](0101_%5B_%5D_END_TO_END_NPM_TRUSTED_PUBLISHING_PLAYBOOK.md) | 2026-06-03 *(default)* | 128d | — |
+| [0102_[_]_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md](0102_%5B_%5D_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md) | 2026-06-03 *(default)* | 128d | — |
+| [0103_[-]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md](0103_%5B-%5D_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md) | 2026-06-04 *(default)* | 127d | — |
+| [0104_[-]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md](0104_%5B-%5D_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md) | 2026-06-04 *(default)* | 127d | — |
+| [0105_[_]_WHAT_TO_WORK_ON_NEXT_AFTER_OPEN_SOURCE_LAUNCH.md](0105_%5B_%5D_WHAT_TO_WORK_ON_NEXT_AFTER_OPEN_SOURCE_LAUNCH.md) | 2026-06-05 *(default)* | 126d | — |
+| [0106_[_]_CI_PERF_TESTING_OPTIONS.md](0106_%5B_%5D_CI_PERF_TESTING_OPTIONS.md) | 2026-06-05 *(default)* | 126d | — |
+| [0106_[_]_JOIN_QUERIES_MULTI_TYPE_AGGREGATES_QUERY_PLANNING_API.md](0106_%5B_%5D_JOIN_QUERIES_MULTI_TYPE_AGGREGATES_QUERY_PLANNING_API.md) | 2026-06-05 *(default)* | 126d | — |
+| [0107_[_]_STORYBOOK_PERFORMANCE_PANEL_AND_ELECTRON_IDE_WORKSHOP.md](0107_%5B_%5D_STORYBOOK_PERFORMANCE_PANEL_AND_ELECTRON_IDE_WORKSHOP.md) | 2026-06-06 *(default)* | 125d | — |
+| [0108_[_]_CANVAS_V1_PAGES_DATABASES_AND_INFINITE_CANVAS_DEEP_DIVE.md](0108_%5B_%5D_CANVAS_V1_PAGES_DATABASES_AND_INFINITE_CANVAS_DEEP_DIVE.md) | 2026-06-07 *(default)* | 124d | — |
+| [0108_[_]_EXPO_APP_PARITY_WITH_ELECTRON_AND_WEB.md](0108_%5B_%5D_EXPO_APP_PARITY_WITH_ELECTRON_AND_WEB.md) | 2026-06-07 *(default)* | 124d | — |
+| [0108_[_]_TIMING_FOR_INTEGRATING_CHAT_AND_VIDEO_INTO_XNET_NOW_VS_LATER.md](0108_%5B_%5D_TIMING_FOR_INTEGRATING_CHAT_AND_VIDEO_INTO_XNET_NOW_VS_LATER.md) | 2026-06-07 *(default)* | 124d | — |
+| [0108_[_]_USEQUERY_UPGRADE_TIMING_AND_INTEGRATION_SEQUENCING.md](0108_%5B_%5D_USEQUERY_UPGRADE_TIMING_AND_INTEGRATION_SEQUENCING.md) | 2026-06-07 *(default)* | 124d | — |
+| [0109_[_]_REPOSITORY_AND_PROJECT_SUMMARY_FOR_NON_TECHNICAL_USERS.md](0109_%5B_%5D_REPOSITORY_AND_PROJECT_SUMMARY_FOR_NON_TECHNICAL_USERS.md) | 2026-06-08 *(default)* | 123d | — |
+| [0110_[_]_XNET_AS_A_VIABLE_WIKIPEDIA_ALTERNATIVE.md](0110_%5B_%5D_XNET_AS_A_VIABLE_WIKIPEDIA_ALTERNATIVE.md) | 2026-07-04 *(default)* | 97d | — |
+| [0111_[_]_UNIFIED_WORKBENCH_ARCHITECTURE_FOR_XNET.md](0111_%5B_%5D_UNIFIED_WORKBENCH_ARCHITECTURE_FOR_XNET.md) | 2026-07-04 *(default)* | 97d | — |
+| [0112_[_]_UNIVERSAL_CLIPPER_AND_AI_KNOWLEDGE_GRAPH_INGESTION.md](0112_%5B_%5D_UNIVERSAL_CLIPPER_AND_AI_KNOWLEDGE_GRAPH_INGESTION.md) | 2026-07-04 *(default)* | 97d | — |
+| [0113_[_]_OTHER_INTERNET_INFRASTRUCTURE_ROLES_FOR_XNET.md](0113_%5B_%5D_OTHER_INTERNET_INFRASTRUCTURE_ROLES_FOR_XNET.md) | 2026-07-04 *(default)* | 97d | — |
+| [0114_[_]_DECENTRALIZED_ALTERNATIVES_FOR_NON_XNET_INTERNET_LAYERS.md](0114_%5B_%5D_DECENTRALIZED_ALTERNATIVES_FOR_NON_XNET_INTERNET_LAYERS.md) | 2026-07-04 *(default)* | 97d | — |
+| [0115_[_]_ARCHITECTING_FULLY_DECENTRALIZED_GLOBAL_WEB_SEARCH.md](0115_%5B_%5D_ARCHITECTING_FULLY_DECENTRALIZED_GLOBAL_WEB_SEARCH.md) | 2026-07-06 *(default)* | 95d | — |
+| [0116_[_]_ARCHITECTING_DECENTRALIZED_TWITTER_X_ON_XNET.md](0116_%5B_%5D_ARCHITECTING_DECENTRALIZED_TWITTER_X_ON_XNET.md) | 2026-07-06 *(default)* | 95d | — |
+| [0117_[_]_ARCHITECTING_DECENTRALIZED_AI_ON_XNET.md](0117_%5B_%5D_ARCHITECTING_DECENTRALIZED_AI_ON_XNET.md) | 2026-07-06 *(default)* | 95d | — |
+| [0118_[_]_ARCHITECTING_A_DECENTRALIZED_OSS_FORGE_ON_XNET.md](0118_%5B_%5D_ARCHITECTING_A_DECENTRALIZED_OSS_FORGE_ON_XNET.md) | 2026-07-06 *(default)* | 95d | — |
+| [0119_[_]_XNET_AS_A_COMPELLING_WEB_AND_MOBILE_DEVELOPER_TOOL.md](0119_%5B_%5D_XNET_AS_A_COMPELLING_WEB_AND_MOBILE_DEVELOPER_TOOL.md) | 2026-07-06 *(default)* | 95d | — |
+| [0120_[_]_XNET_PACKAGE_SECURITY_AND_RELIABILITY_EXPLORATION.md](0120_%5B_%5D_XNET_PACKAGE_SECURITY_AND_RELIABILITY_EXPLORATION.md) | 2026-07-06 *(default)* | 95d | — |
+| [0283_[_]_CI_FAILURE_PATTERNS_AND_PIPELINE_HEALTH.md](0283_%5B_%5D_CI_FAILURE_PATTERNS_AND_PIPELINE_HEALTH.md) | 2026-10-06 *(default)* | 3d | — |
+| [0286_[_]_ALTERNATIVE_CLOUD_HOSTING_SUBSTRATES_COMPUTE_AND_STORAGE_TRADEOFFS.md](0286_%5B_%5D_ALTERNATIVE_CLOUD_HOSTING_SUBSTRATES_COMPUTE_AND_STORAGE_TRADEOFFS.md) | 2026-10-07 *(default)* | 2d | — |
+| [0287_[_]_FLEET_SCALABILITY_SHARP_EDGES_AND_HARD_CAPS.md](0287_%5B_%5D_FLEET_SCALABILITY_SHARP_EDGES_AND_HARD_CAPS.md) | 2026-10-07 *(default)* | 2d | — |
+| [0288_[_]_FULLY_INTEGRATING_LITESTREAM_INTO_THE_CLOUD_OFFERING.md](0288_%5B_%5D_FULLY_INTEGRATING_LITESTREAM_INTO_THE_CLOUD_OFFERING.md) | 2026-10-07 *(default)* | 2d | — |
+| [0288_[_]_WIRING_THE_FLOATING_SHELL_AFFORDANCES.md](0288_%5B_%5D_WIRING_THE_FLOATING_SHELL_AFFORDANCES.md) | 2026-10-07 *(default)* | 2d | — |
+| [0289_[_]_SECURELY_CONNECTING_THE_BROWSER_TO_A_LOCAL_MODEL.md](0289_%5B_%5D_SECURELY_CONNECTING_THE_BROWSER_TO_A_LOCAL_MODEL.md) | 2026-10-07 *(default)* | 2d | — |
+| [0290_[-]_SHARE_LINK_GENERATION_FAILURE_MODES.md](0290_%5B-%5D_SHARE_LINK_GENERATION_FAILURE_MODES.md) | 2026-10-08 *(default)* | 1d | — |
+| [0291_[_]_DEMO_HUB_RUNAWAY_STORAGE_QUOTA_AND_EVICTION_NOT_ENFORCED.md](0291_%5B_%5D_DEMO_HUB_RUNAWAY_STORAGE_QUOTA_AND_EVICTION_NOT_ENFORCED.md) | 2026-10-08 *(default)* | 1d | — |
+| [0293_[_]_DEPOT_DEV_CI_PERFORMANCE_EVALUATION.md](0293_%5B_%5D_DEPOT_DEV_CI_PERFORMANCE_EVALUATION.md) | 2026-10-08 *(default)* | 1d | — |
## Undated
diff --git a/site/public/blog/covers/a-great-pirate-age-800.webp b/site/public/blog/covers/a-great-pirate-age-800.webp
new file mode 100644
index 000000000..3cc784cf4
Binary files /dev/null and b/site/public/blog/covers/a-great-pirate-age-800.webp differ
diff --git a/site/public/blog/covers/a-great-pirate-age.webp b/site/public/blog/covers/a-great-pirate-age.webp
new file mode 100644
index 000000000..90b0ed284
Binary files /dev/null and b/site/public/blog/covers/a-great-pirate-age.webp differ
diff --git a/site/public/blog/covers/atoms-for-the-ballot-800.webp b/site/public/blog/covers/atoms-for-the-ballot-800.webp
new file mode 100644
index 000000000..d292e9c2b
Binary files /dev/null and b/site/public/blog/covers/atoms-for-the-ballot-800.webp differ
diff --git a/site/public/blog/covers/atoms-for-the-ballot.webp b/site/public/blog/covers/atoms-for-the-ballot.webp
new file mode 100644
index 000000000..f22afef37
Binary files /dev/null and b/site/public/blog/covers/atoms-for-the-ballot.webp differ
diff --git a/site/public/blog/covers/clutch-power-800.webp b/site/public/blog/covers/clutch-power-800.webp
new file mode 100644
index 000000000..4f9c4a9d9
Binary files /dev/null and b/site/public/blog/covers/clutch-power-800.webp differ
diff --git a/site/public/blog/covers/clutch-power.webp b/site/public/blog/covers/clutch-power.webp
new file mode 100644
index 000000000..b65150098
Binary files /dev/null and b/site/public/blog/covers/clutch-power.webp differ
diff --git a/site/public/blog/covers/data-should-work-like-soil-800.webp b/site/public/blog/covers/data-should-work-like-soil-800.webp
new file mode 100644
index 000000000..af6106b45
Binary files /dev/null and b/site/public/blog/covers/data-should-work-like-soil-800.webp differ
diff --git a/site/public/blog/covers/data-should-work-like-soil.webp b/site/public/blog/covers/data-should-work-like-soil.webp
new file mode 100644
index 000000000..6f2353553
Binary files /dev/null and b/site/public/blog/covers/data-should-work-like-soil.webp differ
diff --git a/site/public/blog/covers/hand-on-the-tiller-800.webp b/site/public/blog/covers/hand-on-the-tiller-800.webp
new file mode 100644
index 000000000..956bcf225
Binary files /dev/null and b/site/public/blog/covers/hand-on-the-tiller-800.webp differ
diff --git a/site/public/blog/covers/hand-on-the-tiller.webp b/site/public/blog/covers/hand-on-the-tiller.webp
new file mode 100644
index 000000000..e71ff82ce
Binary files /dev/null and b/site/public/blog/covers/hand-on-the-tiller.webp differ
diff --git a/site/public/blog/covers/palimpsest-800.webp b/site/public/blog/covers/palimpsest-800.webp
new file mode 100644
index 000000000..8d2f56f52
Binary files /dev/null and b/site/public/blog/covers/palimpsest-800.webp differ
diff --git a/site/public/blog/covers/palimpsest.webp b/site/public/blog/covers/palimpsest.webp
new file mode 100644
index 000000000..b7773984d
Binary files /dev/null and b/site/public/blog/covers/palimpsest.webp differ
diff --git a/site/public/blog/covers/people-in-disguise-800.webp b/site/public/blog/covers/people-in-disguise-800.webp
new file mode 100644
index 000000000..405843e9f
Binary files /dev/null and b/site/public/blog/covers/people-in-disguise-800.webp differ
diff --git a/site/public/blog/covers/people-in-disguise.webp b/site/public/blog/covers/people-in-disguise.webp
new file mode 100644
index 000000000..4302f5252
Binary files /dev/null and b/site/public/blog/covers/people-in-disguise.webp differ
diff --git a/site/public/blog/covers/rig-the-game-or-play-800.webp b/site/public/blog/covers/rig-the-game-or-play-800.webp
new file mode 100644
index 000000000..deb200a5a
Binary files /dev/null and b/site/public/blog/covers/rig-the-game-or-play-800.webp differ
diff --git a/site/public/blog/covers/rig-the-game-or-play.webp b/site/public/blog/covers/rig-the-game-or-play.webp
new file mode 100644
index 000000000..8b3936093
Binary files /dev/null and b/site/public/blog/covers/rig-the-game-or-play.webp differ
diff --git a/site/public/blog/covers/the-desert-that-feeds-the-forest-800.webp b/site/public/blog/covers/the-desert-that-feeds-the-forest-800.webp
new file mode 100644
index 000000000..e38e8b288
Binary files /dev/null and b/site/public/blog/covers/the-desert-that-feeds-the-forest-800.webp differ
diff --git a/site/public/blog/covers/the-desert-that-feeds-the-forest.webp b/site/public/blog/covers/the-desert-that-feeds-the-forest.webp
new file mode 100644
index 000000000..9baf2e20a
Binary files /dev/null and b/site/public/blog/covers/the-desert-that-feeds-the-forest.webp differ
diff --git a/site/public/blog/covers/the-door-inside-the-house-800.webp b/site/public/blog/covers/the-door-inside-the-house-800.webp
new file mode 100644
index 000000000..6d0842dd0
Binary files /dev/null and b/site/public/blog/covers/the-door-inside-the-house-800.webp differ
diff --git a/site/public/blog/covers/the-door-inside-the-house.webp b/site/public/blog/covers/the-door-inside-the-house.webp
new file mode 100644
index 000000000..3d3569ed9
Binary files /dev/null and b/site/public/blog/covers/the-door-inside-the-house.webp differ
diff --git a/site/public/blog/covers/the-forest-and-the-field-800.webp b/site/public/blog/covers/the-forest-and-the-field-800.webp
new file mode 100644
index 000000000..a79089756
Binary files /dev/null and b/site/public/blog/covers/the-forest-and-the-field-800.webp differ
diff --git a/site/public/blog/covers/the-forest-and-the-field.webp b/site/public/blog/covers/the-forest-and-the-field.webp
new file mode 100644
index 000000000..3d412c043
Binary files /dev/null and b/site/public/blog/covers/the-forest-and-the-field.webp differ
diff --git a/site/public/blog/covers/the-gentlest-furnace-800.webp b/site/public/blog/covers/the-gentlest-furnace-800.webp
new file mode 100644
index 000000000..3f47750a5
Binary files /dev/null and b/site/public/blog/covers/the-gentlest-furnace-800.webp differ
diff --git a/site/public/blog/covers/the-gentlest-furnace.webp b/site/public/blog/covers/the-gentlest-furnace.webp
new file mode 100644
index 000000000..62934d779
Binary files /dev/null and b/site/public/blog/covers/the-gentlest-furnace.webp differ
diff --git a/site/public/blog/covers/the-harvest-you-can-count-800.webp b/site/public/blog/covers/the-harvest-you-can-count-800.webp
new file mode 100644
index 000000000..846bb6933
Binary files /dev/null and b/site/public/blog/covers/the-harvest-you-can-count-800.webp differ
diff --git a/site/public/blog/covers/the-harvest-you-can-count.webp b/site/public/blog/covers/the-harvest-you-can-count.webp
new file mode 100644
index 000000000..0958d39b4
Binary files /dev/null and b/site/public/blog/covers/the-harvest-you-can-count.webp differ
diff --git a/site/public/blog/covers/the-hundred-year-machine-800.webp b/site/public/blog/covers/the-hundred-year-machine-800.webp
new file mode 100644
index 000000000..e4ac1362b
Binary files /dev/null and b/site/public/blog/covers/the-hundred-year-machine-800.webp differ
diff --git a/site/public/blog/covers/the-hundred-year-machine.webp b/site/public/blog/covers/the-hundred-year-machine.webp
new file mode 100644
index 000000000..b4f034738
Binary files /dev/null and b/site/public/blog/covers/the-hundred-year-machine.webp differ
diff --git a/site/public/blog/covers/the-loom-you-can-read-800.webp b/site/public/blog/covers/the-loom-you-can-read-800.webp
new file mode 100644
index 000000000..4734c5402
Binary files /dev/null and b/site/public/blog/covers/the-loom-you-can-read-800.webp differ
diff --git a/site/public/blog/covers/the-loom-you-can-read.webp b/site/public/blog/covers/the-loom-you-can-read.webp
new file mode 100644
index 000000000..fa25124e2
Binary files /dev/null and b/site/public/blog/covers/the-loom-you-can-read.webp differ
diff --git a/site/public/blog/covers/the-matchmaker-and-the-meter-800.webp b/site/public/blog/covers/the-matchmaker-and-the-meter-800.webp
new file mode 100644
index 000000000..c4a749baf
Binary files /dev/null and b/site/public/blog/covers/the-matchmaker-and-the-meter-800.webp differ
diff --git a/site/public/blog/covers/the-matchmaker-and-the-meter.webp b/site/public/blog/covers/the-matchmaker-and-the-meter.webp
new file mode 100644
index 000000000..2b3eb6fb1
Binary files /dev/null and b/site/public/blog/covers/the-matchmaker-and-the-meter.webp differ
diff --git a/site/public/blog/covers/the-right-to-say-no-800.webp b/site/public/blog/covers/the-right-to-say-no-800.webp
new file mode 100644
index 000000000..d76fd5b48
Binary files /dev/null and b/site/public/blog/covers/the-right-to-say-no-800.webp differ
diff --git a/site/public/blog/covers/the-right-to-say-no.webp b/site/public/blog/covers/the-right-to-say-no.webp
new file mode 100644
index 000000000..0b00228b7
Binary files /dev/null and b/site/public/blog/covers/the-right-to-say-no.webp differ
diff --git a/site/public/blog/covers/the-table-and-the-wall-800.webp b/site/public/blog/covers/the-table-and-the-wall-800.webp
new file mode 100644
index 000000000..31187ee51
Binary files /dev/null and b/site/public/blog/covers/the-table-and-the-wall-800.webp differ
diff --git a/site/public/blog/covers/the-table-and-the-wall.webp b/site/public/blog/covers/the-table-and-the-wall.webp
new file mode 100644
index 000000000..d0a3850cf
Binary files /dev/null and b/site/public/blog/covers/the-table-and-the-wall.webp differ
diff --git a/site/public/blog/covers/the-tip-of-the-hook-800.webp b/site/public/blog/covers/the-tip-of-the-hook-800.webp
new file mode 100644
index 000000000..7b600c893
Binary files /dev/null and b/site/public/blog/covers/the-tip-of-the-hook-800.webp differ
diff --git a/site/public/blog/covers/the-tip-of-the-hook.webp b/site/public/blog/covers/the-tip-of-the-hook.webp
new file mode 100644
index 000000000..5961f7a0b
Binary files /dev/null and b/site/public/blog/covers/the-tip-of-the-hook.webp differ
diff --git a/site/public/blog/covers/the-vault-and-the-view-800.webp b/site/public/blog/covers/the-vault-and-the-view-800.webp
new file mode 100644
index 000000000..f2c26c51f
Binary files /dev/null and b/site/public/blog/covers/the-vault-and-the-view-800.webp differ
diff --git a/site/public/blog/covers/the-vault-and-the-view.webp b/site/public/blog/covers/the-vault-and-the-view.webp
new file mode 100644
index 000000000..7c5dc732e
Binary files /dev/null and b/site/public/blog/covers/the-vault-and-the-view.webp differ
diff --git a/site/public/blog/covers/the-workshop-and-the-walled-garden-800.webp b/site/public/blog/covers/the-workshop-and-the-walled-garden-800.webp
new file mode 100644
index 000000000..1d1b43587
Binary files /dev/null and b/site/public/blog/covers/the-workshop-and-the-walled-garden-800.webp differ
diff --git a/site/public/blog/covers/the-workshop-and-the-walled-garden.webp b/site/public/blog/covers/the-workshop-and-the-walled-garden.webp
new file mode 100644
index 000000000..4d238471d
Binary files /dev/null and b/site/public/blog/covers/the-workshop-and-the-walled-garden.webp differ
diff --git a/site/public/blog/covers/the-worlds-greatest-record-store-800.webp b/site/public/blog/covers/the-worlds-greatest-record-store-800.webp
new file mode 100644
index 000000000..36f33f6fe
Binary files /dev/null and b/site/public/blog/covers/the-worlds-greatest-record-store-800.webp differ
diff --git a/site/public/blog/covers/the-worlds-greatest-record-store.webp b/site/public/blog/covers/the-worlds-greatest-record-store.webp
new file mode 100644
index 000000000..e2506359a
Binary files /dev/null and b/site/public/blog/covers/the-worlds-greatest-record-store.webp differ
diff --git a/site/public/blog/covers/timeout-800.webp b/site/public/blog/covers/timeout-800.webp
new file mode 100644
index 000000000..f35c3c96b
Binary files /dev/null and b/site/public/blog/covers/timeout-800.webp differ
diff --git a/site/public/blog/covers/timeout.webp b/site/public/blog/covers/timeout.webp
new file mode 100644
index 000000000..5b881ca1c
Binary files /dev/null and b/site/public/blog/covers/timeout.webp differ
diff --git a/site/public/blog/covers/tree-rings-800.webp b/site/public/blog/covers/tree-rings-800.webp
new file mode 100644
index 000000000..16e5a2b97
Binary files /dev/null and b/site/public/blog/covers/tree-rings-800.webp differ
diff --git a/site/public/blog/covers/tree-rings.webp b/site/public/blog/covers/tree-rings.webp
new file mode 100644
index 000000000..3667f498e
Binary files /dev/null and b/site/public/blog/covers/tree-rings.webp differ
diff --git a/site/public/blog/covers/weights-you-can-hold-800.webp b/site/public/blog/covers/weights-you-can-hold-800.webp
new file mode 100644
index 000000000..5dec6f237
Binary files /dev/null and b/site/public/blog/covers/weights-you-can-hold-800.webp differ
diff --git a/site/public/blog/covers/weights-you-can-hold.webp b/site/public/blog/covers/weights-you-can-hold.webp
new file mode 100644
index 000000000..af5639548
Binary files /dev/null and b/site/public/blog/covers/weights-you-can-hold.webp differ
diff --git a/site/src/components/blog/BallotArt.astro b/site/src/components/blog/BallotArt.astro
deleted file mode 100644
index 9e0525467..000000000
--- a/site/src/components/blog/BallotArt.astro
+++ /dev/null
@@ -1,137 +0,0 @@
----
-// Decorative art for "Atoms for the Ballot" (exploration 0463), single-sourced
-// so the same inline SVG backs the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity).
-//
-// The picture is the essay's closing line. On the left, lamplight: a paper
-// ballot lying on a table, one pencilled cross in one box, the pencil beside
-// it. It is whole, warm and slightly crooked, the way paper is. Moving right the
-// light cools, and the ballot's far edge comes apart into a grid of bits that
-// thin out and drift toward a fibre line running off the frame. Atoms where it
-// must be final; electrons for everything that can be taken back.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// Ballot rows: y offset of each candidate line, and which box carries the cross.
-const ROWS = [0, 34, 68, 102]
-const MARKED = 1
-
-// The dissolving edge: a deterministic scatter of bits (no Math.random, so the
-// hero and the index card render the same picture on every build).
-const BITS: Array<{ x: number; y: number; s: number; o: number; lit: boolean }> = []
-for (let col = 0; col < 26; col++) {
- for (let row = 0; row < 9; row++) {
- const h = (col * 73 + row * 151 + col * row * 17) % 100
- const keep = 96 - col * 3.4
- if (h > keep) continue
- BITS.push({
- x: 560 + col * 17 + ((h * 7) % 9),
- y: 96 + row * 19 + ((h * 3) % 7) + col * 0.8,
- s: Math.max(2, 7 - col * 0.2),
- o: Math.max(0.12, 0.85 - col * 0.03),
- lit: h % 5 === 0
- })
- }
-}
----
-
-
diff --git a/site/src/components/blog/BallotHero.astro b/site/src/components/blog/BallotHero.astro
index 447c26312..b62f7d08e 100644
--- a/site/src/components/blog/BallotHero.astro
+++ b/site/src/components/blog/BallotHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "Atoms for the Ballot" (exploration 0463). The decorative
-// art lives in BallotArt.astro so the same inline SVG backs both this hero
-// and the blog-index preview card.
-import BallotArt from './BallotArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the lamplit ballot sits where the deck lands, so the copy gets its own
- darkness to stand on. Kept here rather than in BallotArt so the index
- card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/BlogArt.astro b/site/src/components/blog/BlogArt.astro
new file mode 100644
index 000000000..7020bdf58
--- /dev/null
+++ b/site/src/components/blog/BlogArt.astro
@@ -0,0 +1,31 @@
+---
+import { blogArt } from '../../data/blog-art'
+
+interface Props {
+ slug: string
+ class?: string
+ sizes?: string
+ eager?: boolean
+}
+
+const {
+ slug,
+ class: className = 'absolute inset-0 h-full w-full object-cover',
+ sizes = '100vw',
+ eager = false
+} = Astro.props
+const art = blogArt(slug)
+---
+
+
diff --git a/site/src/components/blog/BoardArt.astro b/site/src/components/blog/BoardArt.astro
deleted file mode 100644
index 9fa1e2adb..000000000
--- a/site/src/components/blog/BoardArt.astro
+++ /dev/null
@@ -1,167 +0,0 @@
----
-// Decorative art for "Rig the Game or Play" (exploration 0363), single-sourced
-// so the same inline SVG backs the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity).
-//
-// The picture is the essay's thesis: a board read left to right, from a game
-// still being played to a game already won. On the left the properties are
-// tall, varied, differently coloured, crowded with houses — many players
-// making many different moves. Moving right, the heights converge, the colour
-// drains to grey, the houses disappear: the search has stopped. At the far
-// right one gold tower stands over a board where nothing else is happening.
-//
-// Variety is drawn as colour and height; the rig is drawn as their absence.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// Property columns, hand-placed left to right so the decay reads as a slide
-// rather than a formula: [x, width, height, fill, houses].
-// The gold family is live play; zinc is a square nobody contests any more.
-const LIVE = ['#fbbf24', '#f59e0b', '#fcd34d', '#fb923c', '#facc15', '#f97316']
-const FADED = ['#a1834a', '#8a7245', '#6f6350', '#5c5551', '#4b4750', '#413f47']
-const DEAD = '#3f3f46'
-
-// [x, width, height, colour, houses on top]
-const columns = [
- [46, 30, 108, LIVE[0], 3],
- [84, 30, 74, LIVE[2], 2],
- [122, 30, 132, LIVE[1], 4],
- [160, 30, 62, LIVE[4], 1],
- [198, 30, 96, LIVE[3], 3],
- [236, 30, 148, LIVE[5], 4],
- [274, 30, 70, LIVE[0], 2],
- [312, 30, 118, LIVE[2], 3],
- [350, 30, 88, FADED[0], 2],
- [388, 30, 104, LIVE[1], 2],
- [426, 30, 66, FADED[1], 1],
- [464, 30, 92, FADED[0], 1],
- [502, 30, 58, FADED[2], 1],
- [540, 30, 74, FADED[1], 1],
- [578, 30, 50, FADED[3], 0],
- [616, 30, 58, FADED[2], 0],
- [654, 30, 44, FADED[4], 0],
- [692, 30, 46, FADED[3], 0],
- [730, 30, 38, FADED[5], 0],
- [768, 30, 38, DEAD, 0],
- [806, 30, 34, DEAD, 0],
- [962, 30, 32, DEAD, 0],
- [1000, 30, 30, DEAD, 0]
-] as const
-
-const BASELINE = 262
----
-
-
diff --git a/site/src/components/blog/BoardHero.astro b/site/src/components/blog/BoardHero.astro
index c850becae..509660af7 100644
--- a/site/src/components/blog/BoardHero.astro
+++ b/site/src/components/blog/BoardHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "Rig the Game or Play" (exploration 0363). The decorative art
-// lives in BoardArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card.
-import BoardArt from './BoardArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the board art is bright where the deck sits, so the copy gets its
- own darkness to stand on. Kept here rather than in BoardArt so the index
- card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/BrickArt.astro b/site/src/components/blog/BrickArt.astro
deleted file mode 100644
index 3c0898f9a..000000000
--- a/site/src/components/blog/BrickArt.astro
+++ /dev/null
@@ -1,186 +0,0 @@
----
-// Decorative art for blog post #15 (exploration 0319), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The scene draws the essay's argument:
-// on the left, the silo era — two sealed boxes whose connectors don't match
-// (a triangle socket, a round socket), each with a few dim points locked
-// inside. On the right, one baseplate: a ground plane of studs on which
-// bricks of different shapes all snap, because the coupling is shared. One
-// brick hovers just above the plate, mid-lift — the grip lets go; that's the
-// point. Another sits dashed with a keyhole: present, but it won't snap for
-// you without the key. The cosmic-X hangs overhead as the reading lamp, as in
-// the rest of the series. All geometry original; no LEGO trade dress.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The baseplate stud row (x positions along the plate's top edge).
-const studCols = Array.from({ length: 15 }, (_, i) => 448 + i * 34)
-
-// Studs on top of a brick: n studs across a brick starting at x with width w.
-const brickStuds = (x: number, w: number, n: number) =>
- Array.from({ length: n }, (_, i) => x + (w / (n * 2)) * (2 * i + 1))
-
-// The dotted footpath from the silos out to the baseplate.
-const path = [
- [300, 300],
- [348, 294],
- [396, 296],
- [436, 306]
-]
- .map(([x, y]) => `${x},${y}`)
- .join(' ')
----
-
-
diff --git a/site/src/components/blog/BrickHero.astro b/site/src/components/blog/BrickHero.astro
index a4afd4903..f205201bb 100644
--- a/site/src/components/blog/BrickHero.astro
+++ b/site/src/components/blog/BrickHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #15 (exploration 0319). The decorative art lives in
-// BrickArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import BrickArt from './BrickArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/DisguiseArt.astro b/site/src/components/blog/DisguiseArt.astro
deleted file mode 100644
index 113f6c42e..000000000
--- a/site/src/components/blog/DisguiseArt.astro
+++ /dev/null
@@ -1,134 +0,0 @@
----
-// Decorative art for blog post #15 (exploration 0347), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The essay's two figures in one frame:
-// a musical staff — the protocol — whose note-heads are small human figures,
-// running into a dark monolith (the "AI" black box). Inside the box the same
-// figures continue, faint but present, and the cosmic-X glows at the seam:
-// open the black box and it is made of people. Along the bottom, barely
-// there, a strip of piano keys — MIDI, the lock-in the musician heard first.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The staff: five lines, entering the monolith's left face.
-const staffYs = [196, 212, 228, 244, 260]
-const monolith = { x: 660, y: 56, w: 190, h: 250 }
-// Note-figures on the staff (x, y of feet, opacity) — a phrase, not a scale.
-const notes = [
- [104, 212, 0.9],
- [168, 244, 0.75],
- [232, 228, 0.9],
- [300, 196, 0.8],
- [368, 260, 0.7],
- [432, 228, 0.9],
- [500, 212, 0.8],
- [568, 244, 0.85],
- [624, 228, 0.75]
-] as const
-// The same figures, inside the box — fainter, still people.
-const inside = [
- [700, 212, 0.3],
- [738, 244, 0.24],
- [776, 196, 0.28],
- [812, 260, 0.22],
- [752, 148, 0.2],
- [790, 116, 0.16]
-] as const
----
-
-
diff --git a/site/src/components/blog/DisguiseHero.astro b/site/src/components/blog/DisguiseHero.astro
index 7d38465c7..abcdb3f8e 100644
--- a/site/src/components/blog/DisguiseHero.astro
+++ b/site/src/components/blog/DisguiseHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #15 (exploration 0347). The decorative art lives in
-// DisguiseArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import DisguiseArt from './DisguiseArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/DoorHouseArt.astro b/site/src/components/blog/DoorHouseArt.astro
deleted file mode 100644
index 976a63931..000000000
--- a/site/src/components/blog/DoorHouseArt.astro
+++ /dev/null
@@ -1,142 +0,0 @@
----
-// Decorative art for "The Door Inside the House" (exploration 0444), single-sourced
-// so the same inline SVG backs the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity).
-//
-// The picture is the essay's thesis, read left to right. On the left, a house in
-// cross-section, warm and full: shelves of stacked boxes, the work someone put
-// there. A lens on the ceiling sweeps a cone of light across all of it, because
-// the room has to be readable for the room to be useful. Set into an interior
-// wall, lit and open, is the door marked EXIT — it works, and it only works from
-// in here. On the right, the outside face of the same building: a blank slab with
-// no door in it at all, and one small figure standing at the wall in the cold.
-// The lock sits on the outside, where the person can't reach it.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// The stored work: [x, y, w, h] boxes stacked on the interior shelves.
-const BOXES: Array<[number, number, number, number]> = [
- [84, 214, 40, 30],
- [130, 220, 32, 24],
- [168, 210, 44, 34],
- [92, 176, 34, 32],
- [132, 182, 46, 26],
- [186, 176, 30, 32],
- [104, 140, 42, 30],
- [152, 146, 38, 24]
-]
-
-// Dust/among the light: motes the scanning cone catches.
-const MOTES: Array<[number, number, number]> = [
- [232, 132, 2.2],
- [268, 176, 1.6],
- [214, 196, 1.8],
- [286, 148, 1.4],
- [250, 226, 2]
-]
----
-
-
diff --git a/site/src/components/blog/DoorHouseHero.astro b/site/src/components/blog/DoorHouseHero.astro
index ce74cd03c..9012eefe3 100644
--- a/site/src/components/blog/DoorHouseHero.astro
+++ b/site/src/components/blog/DoorHouseHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The Door Inside the House" (exploration 0444). The decorative
-// art lives in DoorHouseArt.astro so the same inline SVG backs both this hero
-// and the blog-index preview card.
-import DoorHouseArt from './DoorHouseArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the room glow sits right where the deck lands, so the copy gets its
- own darkness to stand on. Kept here rather than in DoorHouseArt so the
- index card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/DustArt.astro b/site/src/components/blog/DustArt.astro
deleted file mode 100644
index 8c4cbe5dd..000000000
--- a/site/src/components/blog/DustArt.astro
+++ /dev/null
@@ -1,145 +0,0 @@
----
-// Decorative art for blog post #4 (exploration 0244), extracted from DustHero so
-// the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). A warm plume of Saharan dust arcs
-// over a dark ocean from the African dunes (left) to the Amazon canopy (right),
-// the cosmic-X glowing as the brightest mote riding the apex of the arc.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The dust bridge drawn literally: a quadratic arc from the desert (left) up
-// over the ocean and down onto the forest (right). We sample points along the
-// Bézier and scatter motes that thin out as they travel — most dust falls back
-// to the sea; a faithful fraction makes the whole crossing.
-const P0 = { x: 235, y: 188 } // lifts off a dune crest
-const C = { x: 545, y: 40 } // apex over the mid-Atlantic
-const P2 = { x: 805, y: 150 } // settles onto the canopy
-const bez = (t: number) => {
- const u = 1 - t
- return {
- x: u * u * P0.x + 2 * u * t * C.x + t * t * P2.x,
- y: u * u * P0.y + 2 * u * t * C.y + t * t * P2.y
- }
-}
-const motes = Array.from({ length: 36 }, (_, i) => {
- const t = i / 35
- const p = bez(t)
- // deterministic jitter so the plume reads as a drifting cloud, not a wire
- const j = (i * 7) % 5
- return {
- x: p.x + (j - 2) * 3.2,
- y: p.y + (((i * 3) % 7) - 3),
- r: 2.7 - t * 1.4, // thins out as it crosses
- o: 0.72 - t * 0.34
- }
-})
-const xMote = bez(0.5) // the cosmic-X rides the apex of the crossing
----
-
-
diff --git a/site/src/components/blog/DustHero.astro b/site/src/components/blog/DustHero.astro
index e0b755ac2..d28718f12 100644
--- a/site/src/components/blog/DustHero.astro
+++ b/site/src/components/blog/DustHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #4 (exploration 0244). The decorative art lives in
-// DustArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import DustArt from './DustArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/ForestArt.astro b/site/src/components/blog/ForestArt.astro
deleted file mode 100644
index 55598f3d1..000000000
--- a/site/src/components/blog/ForestArt.astro
+++ /dev/null
@@ -1,133 +0,0 @@
----
-// Decorative art for blog post #6 (exploration 0246), extracted from ForestHero
-// so the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). A flat monocrop field on the left
-// (identical sprouts in dead-straight rows) versus a layered food forest on the
-// right, the cosmic-X glowing as the sun feeding the living half.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The monoculture: identical crops on a rigid grid. Uniformity is the point —
-// a thousand of the same plant, one bad day from collapse.
-const fieldRows = [236, 268, 302, 336]
-const fieldCrop = fieldRows.flatMap((y, r) =>
- Array.from({ length: 7 }, (_, c) => ({ x: 58 + c * 66, y, r }))
-)
-
-// The food forest: hand-placed layers, varied heights and greens, nothing in
-// rows. Canopy high, understory below it, shrubs lower, herb dots at the floor.
-const canopy = [
- { cx: 612, cy: 150, rx: 46, ry: 30, fill: '#1f6b3f' },
- { cx: 712, cy: 132, rx: 58, ry: 38, fill: '#175a34' },
- { cx: 838, cy: 146, rx: 52, ry: 34, fill: '#1f6b3f' },
- { cx: 948, cy: 158, rx: 44, ry: 28, fill: '#175a34' }
-]
-const understory = [
- { cx: 664, cy: 196, rx: 30, ry: 20, fill: '#2f7d4f' },
- { cx: 776, cy: 200, rx: 34, ry: 22, fill: '#2f7d4f' },
- { cx: 898, cy: 198, rx: 28, ry: 19, fill: '#368a57' }
-]
-const shrubs = [
- { cx: 600, cy: 238, rx: 20, ry: 14, fill: '#3f9d63' },
- { cx: 726, cy: 244, rx: 24, ry: 15, fill: '#3f9d63' },
- { cx: 852, cy: 240, rx: 22, ry: 14, fill: '#46a86c' },
- { cx: 960, cy: 236, rx: 18, ry: 12, fill: '#3f9d63' }
-]
-const trunks = [
- { x: 612, y: 178 },
- { x: 712, y: 168 },
- { x: 838, y: 178 },
- { x: 948, y: 184 }
-]
-const herbs = Array.from({ length: 22 }, (_, i) => ({
- x: 566 + i * 21,
- y: 286 + ((i * 5) % 18),
- o: 0.4 + ((i * 7) % 5) * 0.1
-}))
----
-
-
diff --git a/site/src/components/blog/ForestHero.astro b/site/src/components/blog/ForestHero.astro
index eddb0d3f0..850df6b11 100644
--- a/site/src/components/blog/ForestHero.astro
+++ b/site/src/components/blog/ForestHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #6 (exploration 0246). The decorative art lives in
-// ForestArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import ForestArt from './ForestArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/HarvestArt.astro b/site/src/components/blog/HarvestArt.astro
deleted file mode 100644
index ab66069c6..000000000
--- a/site/src/components/blog/HarvestArt.astro
+++ /dev/null
@@ -1,207 +0,0 @@
----
-// Decorative art for "The Harvest You Can Count" (exploration 0368),
-// single-sourced so the same inline SVG backs the post hero and the blog-index
-// preview card (no external assets — Self-Audit parity).
-//
-// The picture is the essay's thesis. On the left, a ruled ledger page: three
-// columns with headings and neat filled entries, everything squared to the
-// rule. Those are the things a ledger has always been able to hold — yield,
-// date, price. To the right the rules keep going but the columns are empty:
-// the headings that were never written, for variance, soil carbon,
-// micronutrients, independence from inputs.
-//
-// And out of that empty half a canopy grows, refusing to sit inside the cells
-// — crossing the margin, ignoring the rules, perfectly real and entirely
-// unrecorded. The ledger is not wrong. It is just short of columns.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// Ledger geometry. The page is ruled all the way across; only the left third
-// carries entries, so the emptiness on the right is visibly the *same* page.
-const RULE_TOP = 96
-const RULE_GAP = 26
-const RULE_COUNT = 7
-const rules = Array.from({ length: RULE_COUNT }, (_, i) => RULE_TOP + i * RULE_GAP)
-
-// Column dividers across the whole page — the grid exists everywhere.
-const columnX = [78, 226, 374, 522, 670, 818, 966]
-
-// The three columns that carry entries: [x, heading width, bar widths per row].
-// Hand-placed so the figures read as a real hand rather than a pattern.
-const filled = [
- { x: 96, head: 86, bars: [72, 88, 64, 92, 58, 80] },
- { x: 244, head: 74, bars: [88, 62, 94, 70, 86, 66] },
- { x: 392, head: 92, bars: [64, 90, 76, 60, 92, 72] }
-] as const
-
-// Canopy: irregular blobs and trunks rising through the unruled half. Sizes and
-// offsets are hand-set — a formula would draw an orchard, and an orchard is a
-// monoculture. [cx, cy, rx, ry, opacity]
-const canopy = [
- [604, 176, 74, 52, 0.9],
- [700, 138, 92, 62, 0.85],
- [806, 182, 80, 54, 0.9],
- [900, 146, 86, 58, 0.82],
- [658, 218, 64, 42, 0.8],
- [762, 232, 72, 44, 0.78],
- [862, 224, 60, 40, 0.8],
- [956, 206, 68, 46, 0.75]
-] as const
-
-// [x, top y, height, width]
-const trunks = [
- [602, 176, 92, 7],
- [698, 140, 126, 8],
- [804, 182, 88, 7],
- [898, 148, 118, 8],
- [760, 232, 44, 6]
-] as const
----
-
-
diff --git a/site/src/components/blog/HarvestHero.astro b/site/src/components/blog/HarvestHero.astro
index 865a09f51..cd93a807d 100644
--- a/site/src/components/blog/HarvestHero.astro
+++ b/site/src/components/blog/HarvestHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The Harvest You Can Count" (exploration 0368). The decorative
-// art lives in HarvestArt.astro so the same inline SVG backs both this hero and
-// the blog-index preview card.
-import HarvestArt from './HarvestArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the ledger art is bright under the lamp, exactly where the deck
- sits, so the copy gets its own darkness to stand on. Kept here rather than
- in HarvestArt so the index card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/HookArt.astro b/site/src/components/blog/HookArt.astro
deleted file mode 100644
index d54f5fa52..000000000
--- a/site/src/components/blog/HookArt.astro
+++ /dev/null
@@ -1,126 +0,0 @@
----
-// Decorative art for the React-hooks deep-dive ("The Tip of the Hook",
-// exploration 0249), extracted from HookHero so the same inline SVG backs both
-// the post hero and the blog-index preview card (no external assets — Self-Audit
-// parity). The motif: an iceberg. A small lit tip rides above the waterline —
-// the eight readable lines of a `useQuery` — while a vast faceted mass descends
-// into the deep: the worker, the SQLite database, the scheduler, the sync
-// engine. The cosmic-X glows at the core of the submerged machine, the single
-// brightest thing under the surface. IDs are prefixed `toh*` so they never
-// collide with LoomArt's `lh*` defs when both render on the index page.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Faint horizontal strata in the deep — the layers of machinery beneath the
-// surface, drawn as dashed lines that fade with depth.
-const strata = [196, 224, 252, 280, 308]
-
-// Bubbles rising from the submerged engine toward the light.
-const bubbles = [
- { cx: 470, cy: 250, r: 3 },
- { cx: 588, cy: 232, r: 2.4 },
- { cx: 512, cy: 300, r: 2 },
- { cx: 556, cy: 270, r: 1.6 },
- { cx: 446, cy: 296, r: 1.8 }
-]
----
-
-
diff --git a/site/src/components/blog/HookHero.astro b/site/src/components/blog/HookHero.astro
index 5ad0a3d03..95fc618f6 100644
--- a/site/src/components/blog/HookHero.astro
+++ b/site/src/components/blog/HookHero.astro
@@ -1,9 +1,5 @@
---
-// Hero band for the React-hooks deep-dive ("The Tip of the Hook", exploration
-// 0249). The decorative art lives in HookArt.astro so the same inline SVG backs
-// both this hero and the blog-index preview card; this component layers the
-// post's title/deck over it.
-import HookArt from './HookArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -16,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/HundredYearArt.astro b/site/src/components/blog/HundredYearArt.astro
deleted file mode 100644
index 02d64c2b8..000000000
--- a/site/src/components/blog/HundredYearArt.astro
+++ /dev/null
@@ -1,132 +0,0 @@
----
-// Decorative art for "The Hundred-Year Machine" (exploration 0454), single-sourced
-// so the same inline SVG backs the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity).
-//
-// The picture is the essay's closing walk, read left to right. The apartment at
-// night: a lamp throwing warm light over the machines that survive their makers —
-// the treadle sewing machine, the bicycle, the kettle on its ring. Each keeps a
-// small live glow. On the right, the newest objects: a flat screen and a smart
-// speaker, dark, with a power cord that ends mid-air — plugged into a company
-// rather than a wall. The moon through the window has been working longest of all.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// Floorboard seams, [x1, x2] pairs along the floor line.
-const BOARDS: Array<[number, number]> = [
- [40, 180],
- [200, 360],
- [380, 560],
- [580, 760],
- [780, 1000]
-]
-
-// Sparks of live warmth on the owned machines: [cx, cy, r].
-const GLOWS: Array<[number, number, number]> = [
- [214, 118, 3],
- [418, 226, 2.4],
- [560, 246, 2.6]
-]
----
-
-
diff --git a/site/src/components/blog/HundredYearHero.astro b/site/src/components/blog/HundredYearHero.astro
index d3869bdfb..28acb4c81 100644
--- a/site/src/components/blog/HundredYearHero.astro
+++ b/site/src/components/blog/HundredYearHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The Hundred-Year Machine" (exploration 0454). The decorative
-// art lives in HundredYearArt.astro so the same inline SVG backs both this hero
-// and the blog-index preview card.
-import HundredYearArt from './HundredYearArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the lamp glow sits where the deck lands, so the copy gets its own
- darkness to stand on. Kept here rather than in HundredYearArt so the index
- card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/LeverArt.astro b/site/src/components/blog/LeverArt.astro
deleted file mode 100644
index 626d58af3..000000000
--- a/site/src/components/blog/LeverArt.astro
+++ /dev/null
@@ -1,110 +0,0 @@
----
-// Decorative art for blog post #5 (exploration 0245), extracted from LeverHero so
-// the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). A lever: a small force on a long arm
-// (the cosmic-X, glowing rose) pivots on a fulcrum and lifts a mass many times
-// its weight on the short arm — "asymmetrical leverage" drawn literally.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Fulcrum apex and the beam it pivots. The beam is tilted: the long left arm is
-// pressed down by a small force, so the short right arm — carrying the heavy
-// mass — is levered up. Pivot lies on the beam at x = apexX.
-const apexX = 545
-const apexY = 176
-const beam = { x1: 250, y1: 206, x2: 770, y2: 150 }
-// Point on the beam directly above the fulcrum (so the apex touches the beam).
-const t = (apexX - beam.x1) / (beam.x2 - beam.x1)
-const pivotY = beam.y1 + t * (beam.y2 - beam.y1)
-
-// The cosmic-X rides the long arm as the small force; the mass sits on the short
-// arm. Endpoints of the beam (the X end and the mass end).
-const forceX = beam.x1 + 24
-const forceY = beam.y1 - 4
-const massX = beam.x2 - 30
-const massY = beam.y2 - 4
----
-
-
diff --git a/site/src/components/blog/LeverHero.astro b/site/src/components/blog/LeverHero.astro
index 7133d5c8e..2cb2dac3a 100644
--- a/site/src/components/blog/LeverHero.astro
+++ b/site/src/components/blog/LeverHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #5 (exploration 0245). The decorative art lives in
-// LeverArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import LeverArt from './LeverArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/LoomArt.astro b/site/src/components/blog/LoomArt.astro
deleted file mode 100644
index d4148ef55..000000000
--- a/site/src/components/blog/LoomArt.astro
+++ /dev/null
@@ -1,95 +0,0 @@
----
-// Decorative art for the internals post (exploration 0248), extracted from
-// LoomHero so the same inline SVG backs both the post hero and the blog-index
-// preview card (no external assets — Self-Audit parity). A loom: taut vertical
-// warp threads, a woven band of weft crossing through, and the cosmic-X riding
-// the shuttle as the single brightest thread in the cloth (indigo).
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Warp: taut vertical threads spanning the frame, faint and parallel.
-const warp = Array.from({ length: 33 }, (_, i) => ({
- x: 20 + i * 31,
- o: 0.16 + ((i * 7) % 5) * 0.05
-}))
-
-// Weft: horizontal threads woven through the central band. Each is drawn as a
-// run of short dashes to suggest passing over and under the warp.
-const weftRows = [150, 168, 186, 204, 222]
----
-
-
diff --git a/site/src/components/blog/LoomHero.astro b/site/src/components/blog/LoomHero.astro
index 2d804de68..4f87c1c81 100644
--- a/site/src/components/blog/LoomHero.astro
+++ b/site/src/components/blog/LoomHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for the internals post (exploration 0248). The decorative art lives
-// in LoomArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import LoomArt from './LoomArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/MeterArt.astro b/site/src/components/blog/MeterArt.astro
deleted file mode 100644
index 04c456ff0..000000000
--- a/site/src/components/blog/MeterArt.astro
+++ /dev/null
@@ -1,160 +0,0 @@
----
-// Decorative art for "The Matchmaker and the Meter" (exploration 0417),
-// single-sourced so the same inline SVG backs the post hero and the blog-index
-// preview card (no external assets — Self-Audit parity).
-//
-// The picture is the essay's thesis, read left to right. On the left, a
-// promenade at dusk: string lights, and people standing in warm pairs — the
-// dance, the paseo, contact that costs nothing. Moving right the light drains,
-// and the same evening ends at a vending cabinet: hearts behind glass, a coin
-// slot, a queue of people standing alone. Connection is drawn as warmth and
-// nearness; the meter is drawn as glass between you and the same thing.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// A small heart path centred on (0, 4), used for the cabinet's stock.
-const HEART =
- 'M 0 -3 C -3 -9, -11 -5, -11 1 C -11 6, -4 10, 0 14 C 4 10, 11 6, 11 1 C 11 -5, 3 -9, 0 -3 Z'
-
-// The pairs on the promenade: [x, y, warm colour]. Hand-placed so the crowd
-// reads as an evening, not a grid.
-const PAIRS: Array<[number, number, string]> = [
- [142, 236, '#fb7185'],
- [218, 244, '#fbbf24'],
- [292, 232, '#f472b6'],
- [364, 246, '#fb923c'],
- [438, 238, '#fda4af']
-]
-
-// The queue at the cabinet: single figures, evenly spaced, drained of colour.
-const QUEUE: Array<[number, number]> = [
- [596, 244],
- [644, 246],
- [690, 248],
- [734, 250]
-]
-
-// Hearts in stock: a 3×4 grid inside the cabinet, dimming towards the bottom
-// row — inventory, not affection.
-const STOCK: Array<[number, number, number]> = []
-for (let row = 0; row < 3; row++) {
- for (let col = 0; col < 4; col++) {
- STOCK.push([812 + col * 34, 92 + row * 46, 0.75 - row * 0.18])
- }
-}
-
-// String lights: two swags across the promenade end of the scene.
-const LIGHTS_A = [60, 120, 180, 240, 300, 360, 420]
-const LIGHTS_B = [100, 170, 240, 310, 380]
----
-
-
diff --git a/site/src/components/blog/MeterHero.astro b/site/src/components/blog/MeterHero.astro
index c2d3b972a..8e0100100 100644
--- a/site/src/components/blog/MeterHero.astro
+++ b/site/src/components/blog/MeterHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The Matchmaker and the Meter" (exploration 0417). The
-// decorative art lives in MeterArt.astro so the same inline SVG backs both this
-// hero and the blog-index preview card.
-import MeterArt from './MeterArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the promenade glow is bright where the deck sits, so the copy gets
- its own darkness to stand on. Kept here rather than in MeterArt so the
- index card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/MycelialArt.astro b/site/src/components/blog/MycelialArt.astro
deleted file mode 100644
index b27b36513..000000000
--- a/site/src/components/blog/MycelialArt.astro
+++ /dev/null
@@ -1,130 +0,0 @@
----
-// Decorative art for blog post #2 (exploration 0240), extracted from MycelialHero
-// so the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). A soil cross-section where the
-// cosmic-X glows as one luminous node in a branching mycelial web.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Hub nodes in the underground web (where hyphae fuse / a root tip meets fungus).
-// Hand-placed below the soil line (y≈150) so the composition reads as a network
-// permeating the substrate. Node index 3 is the bright cosmic-X hub.
-const nodes = [
- { x: 150, y: 250, r: 4 },
- { x: 300, y: 210, r: 5 },
- { x: 430, y: 285, r: 4 },
- { x: 545, y: 235, r: 9 }, // the cosmic-X hub
- { x: 690, y: 280, r: 5 },
- { x: 815, y: 215, r: 4 },
- { x: 930, y: 265, r: 5 }
-]
-// Hyphae fusing a subset of nodes — most threads pass through the central hub,
-// the way a healthy network routes through many paths rather than one centre.
-const hyphae = [
- [0, 1],
- [1, 3],
- [0, 3],
- [2, 3],
- [3, 4],
- [3, 5],
- [4, 6],
- [5, 6],
- [1, 2]
-]
-// Fine hair-like filaments radiating from each hub into the substrate.
-const filaments = nodes.flatMap((n, i) =>
- [0, 1, 2].map((k) => {
- const angle = (i * 1.7 + k * 2.3) % (Math.PI * 2)
- const len = 18 + ((i + k) % 3) * 10
- return {
- x1: n.x,
- y1: n.y,
- x2: n.x + Math.cos(angle) * len,
- y2: n.y + Math.sin(angle) * len * 0.7
- }
- })
-)
----
-
-
diff --git a/site/src/components/blog/MycelialHero.astro b/site/src/components/blog/MycelialHero.astro
index 98905d4e7..2fb55ae25 100644
--- a/site/src/components/blog/MycelialHero.astro
+++ b/site/src/components/blog/MycelialHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #2 (exploration 0240). The decorative art lives in
-// MycelialArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import MycelialArt from './MycelialArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/PalimpsestArt.astro b/site/src/components/blog/PalimpsestArt.astro
deleted file mode 100644
index 97d4830b5..000000000
--- a/site/src/components/blog/PalimpsestArt.astro
+++ /dev/null
@@ -1,175 +0,0 @@
----
-// Decorative art for blog post #17 (exploration 0354), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — the series' "loads nothing third-party" promise holds
-// literally). The essay's argument in one frame: on the left, a palimpsest —
-// faint amber underwriting running vertically (the pages were rotated when
-// they were reused), with a ghost of an Archimedean circle diagram, struck
-// through by the bold violet lines of the overwriting. On the right, the
-// alternative: a signed change log, where both layers survive side by side —
-// nothing scraped, every block keeping its signature — ending at the
-// cosmic-X, still legible. The knife saves nothing here.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// --- left page: the palimpsest ---
-// Underwriting: short vertical strokes in loose columns (the erased text,
-// perpendicular to the prayers because the reused pages were turned 90°).
-const underCols = [120, 160, 200, 240, 280, 320, 360] as const
-// Overwriting: bold horizontal lines (the prayer book), with ragged ends.
-const overLines = [
- [96, 64, 300],
- [96, 96, 268],
- [96, 128, 312],
- [96, 160, 240],
- [96, 192, 296],
- [96, 224, 258],
- [96, 256, 306]
-] as const
-
-// --- right page: the log ---
-// A chain of signed blocks; each keeps BOTH layers (an amber under-stroke and
-// a violet over-stroke) — accretion instead of erasure.
-const blocks = [560, 634, 708, 782, 856] as const
-const blockY = 176
----
-
-
diff --git a/site/src/components/blog/PalimpsestHero.astro b/site/src/components/blog/PalimpsestHero.astro
index a60705d50..6834ee56d 100644
--- a/site/src/components/blog/PalimpsestHero.astro
+++ b/site/src/components/blog/PalimpsestHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #17 (exploration 0354). The decorative art lives in
-// PalimpsestArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import PalimpsestArt from './PalimpsestArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/PirateArt.astro b/site/src/components/blog/PirateArt.astro
deleted file mode 100644
index dd7c1b3f3..000000000
--- a/site/src/components/blog/PirateArt.astro
+++ /dev/null
@@ -1,109 +0,0 @@
----
-// Decorative art for the flagship essay (exploration 0239), extracted from
-// PirateHero so the same inline SVG backs both the post hero and the blog-index
-// preview card (no external assets — Self-Audit parity). An xNet "cosmic-X" flag
-// flown over a sea of scattered islands joined by dotted sea-lanes.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// A few islands + the sea-lanes that connect a subset of them. Hand-placed so the
-// composition reads as "most islands never connect; some choose to."
-const islands = [
- { x: 120, y: 250, r: 10 },
- { x: 250, y: 215, r: 7 },
- { x: 400, y: 260, r: 13 },
- { x: 545, y: 225, r: 8 },
- { x: 680, y: 255, r: 11 },
- { x: 815, y: 220, r: 7 },
- { x: 930, y: 250, r: 12 },
-]
-const lanes = [
- [0, 2],
- [2, 4],
- [4, 6],
- [2, 3],
-]
----
-
-
diff --git a/site/src/components/blog/PirateHero.astro b/site/src/components/blog/PirateHero.astro
index 312fb6269..9a163b33e 100644
--- a/site/src/components/blog/PirateHero.astro
+++ b/site/src/components/blog/PirateHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for the flagship essay (exploration 0239). The decorative art lives
-// in PirateArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import PirateArt from './PirateArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/RecordArt.astro b/site/src/components/blog/RecordArt.astro
deleted file mode 100644
index 0fd8c59d4..000000000
--- a/site/src/components/blog/RecordArt.astro
+++ /dev/null
@@ -1,161 +0,0 @@
----
-// Decorative art for "The World's Greatest Record Store" (exploration 0352),
-// single-sourced so the same inline SVG backs the post hero and the blog-index
-// preview card (no external assets — Self-Audit parity). A pink-lit record
-// shop after dark: shelves of crates, and a vinyl disc in the foreground whose
-// label is the glowing cosmic-X — the scene's treasure, held at the centre.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// Crate spines, hand-placed per shelf so the rows read as loved, not generated:
-// [x, width, hue-index] — hues cycle through the pink/rose/violet family.
-const SPINE_COLORS = ['#f472b6', '#e879f9', '#fb7185', '#c084fc', '#f9a8d4', '#a78bfa']
-const shelfA = [
- [40, 14], [58, 10], [72, 16], [92, 12], [108, 9], [121, 15], [140, 11], [155, 13],
- [172, 10], [186, 16], [206, 12], [222, 9], [235, 14], [253, 11], [268, 15], [287, 10]
-] as const
-const shelfB = [
- [40, 12], [56, 15], [75, 9], [88, 13], [105, 11], [120, 16], [140, 10], [154, 14],
- [172, 12], [188, 9], [201, 15], [220, 11], [235, 13], [252, 10], [266, 16], [286, 11]
-] as const
----
-
-
diff --git a/site/src/components/blog/RecordHero.astro b/site/src/components/blog/RecordHero.astro
index 5e0bfc70a..8f032c18a 100644
--- a/site/src/components/blog/RecordHero.astro
+++ b/site/src/components/blog/RecordHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The World's Greatest Record Store" (exploration 0352). The
-// decorative art lives in RecordArt.astro so the same inline SVG backs both
-// this hero and the blog-index preview card.
-import RecordArt from './RecordArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/RingsArt.astro b/site/src/components/blog/RingsArt.astro
deleted file mode 100644
index 651124dbc..000000000
--- a/site/src/components/blog/RingsArt.astro
+++ /dev/null
@@ -1,154 +0,0 @@
----
-// Decorative art for blog post #16 (exploration 0348), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The essay's argument in one frame:
-// a change log — a hash chain of small signed blocks — enters from the left
-// and curls into the cross-section of a tree. The log *is* the tree. Growth
-// accretes on the outside ring by ring, the interior never changes, and the
-// whole history stays legible in the grain. At the core, the cosmic-X: the
-// first change, still there, still readable. A few faint year ticks run along
-// the horizontal radius the way a dendrochronologist would count them.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The trunk cross-section, centred right of frame.
-const core = { x: 780, y: 172 }
-// Rings: radius, stroke width, opacity, dash pattern (irregular years — wide
-// rings are wet years, tight rings are droughts; none of them get edited).
-const rings = [
- [18, 1.6, 0.65, ''],
- [30, 1.3, 0.55, ''],
- [40, 1.1, 0.5, '52 9'],
- [54, 1.5, 0.6, ''],
- [63, 1, 0.42, '34 7'],
- [76, 1.3, 0.52, ''],
- [92, 1.1, 0.45, '80 12'],
- [104, 1.5, 0.55, ''],
- [113, 1, 0.38, '26 6'],
- [126, 1.3, 0.48, ''],
- [138, 1.1, 0.4, '110 14'],
- [148, 2.6, 0.6, '']
-] as const
-// The change log: a hash chain of signed blocks walking in from the left.
-const blocks = [64, 128, 192, 256, 320, 384, 448] as const
-const blockY = 172
----
-
-
diff --git a/site/src/components/blog/RingsHero.astro b/site/src/components/blog/RingsHero.astro
index 6c17cc5fa..ce9106fbc 100644
--- a/site/src/components/blog/RingsHero.astro
+++ b/site/src/components/blog/RingsHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #16 (exploration 0348). The decorative art lives in
-// RingsArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import RingsArt from './RingsArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/StarArt.astro b/site/src/components/blog/StarArt.astro
deleted file mode 100644
index 49a613c5c..000000000
--- a/site/src/components/blog/StarArt.astro
+++ /dev/null
@@ -1,126 +0,0 @@
----
-// Decorative art for blog post #3 (exploration 0241), extracted from StarHero so
-// the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). The cosmic-X as a star in hydrostatic
-// balance: gravity pulling IN (slate arrows) against radiation pushing OUT (amber
-// arrows), ringed by the orbits it quietly provides for.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The star sits a little above centre so the title below doesn't bury it.
-const cx = 545
-const cy = 150
-
-// Hydrostatic equilibrium drawn literally: two opposing sets of radial arrows.
-// Radiation pushes outward; gravity pulls inward. They interleave (offset by
-// half a step) and meet in the middle — the balance that keeps a star from
-// exploding or collapsing.
-const STEP = Math.PI / 4 // eight directions
-const radiation = Array.from({ length: 8 }, (_, k) => {
- const a = k * STEP
- return {
- x1: cx + Math.cos(a) * 60,
- y1: cy + Math.sin(a) * 60,
- x2: cx + Math.cos(a) * 88,
- y2: cy + Math.sin(a) * 88
- }
-})
-const gravity = Array.from({ length: 8 }, (_, k) => {
- const a = k * STEP + STEP / 2
- return {
- x1: cx + Math.cos(a) * 124,
- y1: cy + Math.sin(a) * 124,
- x2: cx + Math.cos(a) * 100,
- y2: cy + Math.sin(a) * 100
- }
-})
-
-// The orbits the star provides for — a few planets riding elliptical paths.
-const orbits = [
- { rx: 175, ry: 58 },
- { rx: 250, ry: 84 },
- { rx: 330, ry: 110 }
-]
-const planets = [
- { rx: 175, ry: 58, t: 0.62, r: 3 },
- { rx: 250, ry: 84, t: 2.3, r: 4 },
- { rx: 330, ry: 110, t: 3.9, r: 2.6 }
-]
----
-
-
diff --git a/site/src/components/blog/StarHero.astro b/site/src/components/blog/StarHero.astro
index 08f05afde..71c6f0eaa 100644
--- a/site/src/components/blog/StarHero.astro
+++ b/site/src/components/blog/StarHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #3 (exploration 0241). The decorative art lives in
-// StarArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import StarArt from './StarArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/TableWallArt.astro b/site/src/components/blog/TableWallArt.astro
deleted file mode 100644
index 7bd4d5b0e..000000000
--- a/site/src/components/blog/TableWallArt.astro
+++ /dev/null
@@ -1,160 +0,0 @@
----
-// Decorative art for "The Table and the Wall" (exploration 0443), single-sourced
-// so the same inline SVG backs the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity).
-//
-// The picture is the essay's thesis, read left to right. On the left, a warm
-// parlour: a wooden table mid-séance, tipped up on two legs and dancing, with
-// the pale ghosts of the people who made it fading behind it — the commodity
-// forgetting its makers. On the right, a cold concrete wall in cross-section,
-// and every fixture on it — the tower lamp, the angled posts, the wire — faces
-// left, back at its own side. Between the two halves runs a single signal wire,
-// snapped in the middle: the severed channel both halves of the essay are about.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } = Astro.props
-
-// The makers behind the table: [x, y, opacity] — deliberately faded, mid-vanish.
-const MAKERS: Array<[number, number, number]> = [
- [96, 210, 0.35],
- [148, 200, 0.25],
- [206, 214, 0.18]
-]
-
-// The people on the inside of the wall: a small grey crowd, all of it to the
-// left of the slab, none of it to the right.
-const INSIDE: Array<[number, number]> = [
- [676, 252],
- [712, 258],
- [746, 250],
- [706, 274],
- [744, 276]
-]
-
-// Motion arcs under the dancing table's airborne legs.
-const ARCS = [
- 'M 300 236 Q 316 224 332 236',
- 'M 292 252 Q 314 236 336 252',
- 'M 284 268 Q 312 248 340 268'
-]
----
-
-
diff --git a/site/src/components/blog/TableWallHero.astro b/site/src/components/blog/TableWallHero.astro
index 0e1cb6dc2..3fbf7cfec 100644
--- a/site/src/components/blog/TableWallHero.astro
+++ b/site/src/components/blog/TableWallHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for "The Table and the Wall" (exploration 0443). The decorative art
-// lives in TableWallArt.astro so the same inline SVG backs both this hero and
-// the blog-index preview card.
-import TableWallArt from './TableWallArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,15 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
- {/* Scrim: the parlour glow is bright where the deck sits, so the copy gets its
- own darkness to stand on. Kept here rather than in TableWallArt so the
- index card renders the illustration undimmed. */}
+
-
+ >
diff --git a/site/src/components/blog/TillerArt.astro b/site/src/components/blog/TillerArt.astro
deleted file mode 100644
index c8e827b86..000000000
--- a/site/src/components/blog/TillerArt.astro
+++ /dev/null
@@ -1,129 +0,0 @@
----
-// Decorative art for blog post #9 (exploration 0256), extracted from TillerHero so
-// the same inline SVG backs both the post hero and the blog-index preview card
-// (no external assets — Self-Audit parity). A small boat holds a line across open
-// water toward a fixed guiding star: the cosmic-X, glowing sky-blue. The dotted
-// wake behind it zig-zags — constant small course corrections — and the swings
-// shrink as the boat settles onto the bearing. Steering, drawn literally: you
-// never arrive at "aligned"; you hold a course.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The guiding star (cosmic-X) and the boat; the bearing line runs between them.
-const star = { x: 838, y: 92 }
-const boat = { x: 300, y: 236 }
-
-// The wake: the course already sailed, oscillating around the bearing and
-// converging on it as it nears the boat (amplitude decays left→right).
-const wake = [
- [64, 250],
- [98, 231],
- [131, 251],
- [165, 233],
- [199, 248],
- [232, 234],
- [265, 245],
- [298, 237]
-]
- .map(([x, y]) => `${x},${y}`)
- .join(' ')
----
-
-
diff --git a/site/src/components/blog/TillerHero.astro b/site/src/components/blog/TillerHero.astro
index 1e95225c3..8b1f94267 100644
--- a/site/src/components/blog/TillerHero.astro
+++ b/site/src/components/blog/TillerHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #9 (exploration 0256). The decorative art lives in
-// TillerArt.astro so the same inline SVG backs both this hero and the blog-index
-// preview card; this component layers the post's title/deck over it.
-import TillerArt from './TillerArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/TimeoutArt.astro b/site/src/components/blog/TimeoutArt.astro
deleted file mode 100644
index 5fef79070..000000000
--- a/site/src/components/blog/TimeoutArt.astro
+++ /dev/null
@@ -1,170 +0,0 @@
----
-// Decorative art for blog post #12 (exploration 0284), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The scene is the essay drawn
-// literally: bottom-left, a figure resting in a recliner — a peer that has
-// gone quiet — with one softly glowing node at the chest. Upper right, the
-// mesh it dreamed: a constellation of peers joined by faint edges, with one
-// small paused clock hanging among them (the called timeout — the clock is
-// stopped, not broken). Between the two, the cables of a nervous system being
-// plugged back in one at a time: the nearest strands solid and lit, the
-// farther ones dim, the last still dashed and reaching. The cosmic-X hangs
-// overhead as the reading lamp. Nothing is severed; everything is
-// reconnecting.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// The dreamed mesh: peer nodes (x, y, r) in the upper right.
-const peers: Array<[number, number, number]> = [
- [520, 150, 5],
- [610, 96, 6],
- [700, 168, 5],
- [788, 84, 6],
- [864, 152, 5],
- [946, 100, 5],
- [716, 52, 4.4]
-]
-// Edges of the mesh (index pairs into `peers`) — every peer keeps its own
-// copy; the links are between equals.
-const edges: Array<[number, number]> = [
- [0, 1],
- [1, 2],
- [1, 6],
- [2, 3],
- [3, 4],
- [3, 6],
- [4, 5],
- [2, 4]
-]
-// The cables from the chest node up into the mesh, plugged in one at a time:
-// each strand is [points, state]; state 'lit' | 'dim' | 'dashed'.
-const chest: [number, number] = [214, 224]
-const cables: Array<{ to: [number, number]; state: 'lit' | 'dim' | 'dashed' }> = [
- { to: [520, 150], state: 'lit' },
- { to: [610, 96], state: 'lit' },
- { to: [700, 168], state: 'dim' },
- { to: [788, 84], state: 'dashed' }
-]
-// A gentle sag for each cable, rendered as a quadratic curve.
-const cablePath = ([x2, y2]: [number, number]) => {
- const [x1, y1] = chest
- const mx = (x1 + x2) / 2
- const my = Math.max(y1, y2) + 42
- return `M ${x1} ${y1} Q ${mx} ${my} ${x2} ${y2}`
-}
----
-
-
diff --git a/site/src/components/blog/TimeoutHero.astro b/site/src/components/blog/TimeoutHero.astro
index a1eb5502b..af6c797d3 100644
--- a/site/src/components/blog/TimeoutHero.astro
+++ b/site/src/components/blog/TimeoutHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #12 (exploration 0284). The decorative art lives in
-// TimeoutArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import TimeoutArt from './TimeoutArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/VaultArt.astro b/site/src/components/blog/VaultArt.astro
deleted file mode 100644
index 6d5f2543c..000000000
--- a/site/src/components/blog/VaultArt.astro
+++ /dev/null
@@ -1,208 +0,0 @@
----
-// Decorative art for blog post #11 (exploration 0281), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The scene is the essay's title drawn
-// literally: on the left, a bank vault — a sealed circular door with a dial
-// and rivets, and a small barred slot behind which a few dim data points sit
-// locked away. On the right, the inversion: one glowing hash-chained substrate
-// running along the ground, holding the same points in the open, with three
-// floating window frames above it — list, grid, timeline — each looking onto
-// the same data and drawing sight-lines down to it. The cosmic-X hangs
-// overhead as the reading lamp. A dotted footpath runs from the vault to the
-// substrate: the walk from custody to ownership.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Rivets around the vault door rim (angle in degrees).
-const rivets = Array.from({ length: 12 }, (_, i) => {
- const a = (i * 30 * Math.PI) / 180
- return [170 + Math.cos(a) * 96, 190 + Math.sin(a) * 96]
-})
-// The hash-chained substrate blocks along the ground on the right.
-const chain = [430, 520, 610, 700, 790, 880]
-// The footpath from the vault out to the substrate.
-const path = [
- [292, 306],
- [340, 300],
- [388, 302],
- [430, 314]
-]
- .map(([x, y]) => `${x},${y}`)
- .join(' ')
-// The shared data points sitting on the substrate (x, y).
-const nodes = [
- [455, 262],
- [545, 250],
- [635, 266],
- [725, 248],
- [815, 260],
- [905, 252]
-]
----
-
-
diff --git a/site/src/components/blog/VaultHero.astro b/site/src/components/blog/VaultHero.astro
index e735a4c81..52bb2d374 100644
--- a/site/src/components/blog/VaultHero.astro
+++ b/site/src/components/blog/VaultHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #11 (exploration 0281). The decorative art lives in
-// VaultArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import VaultArt from './VaultArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/WeightsArt.astro b/site/src/components/blog/WeightsArt.astro
deleted file mode 100644
index 420e1ae12..000000000
--- a/site/src/components/blog/WeightsArt.astro
+++ /dev/null
@@ -1,116 +0,0 @@
----
-// Decorative art for blog post #13 (exploration 0292), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). A two-pan balance, tipped: on one side
-// a stack of brass kilogram weights — the things you can hold — and on the
-// other the cosmic-X, glowing amber, weighing in with them rather than against
-// them. Behind it, faint, the essay's other figure: history as a spiral.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Beam geometry: the pivot sits on the column; the beam tips toward the
-// weights side (owned things have weight; rented things weigh nothing).
-const pivotX = 520
-const pivotY = 128
-const beam = { x1: 300, y1: 152, x2: 740, y2: 108 }
-// Pan drop-lines hang from the beam ends.
-const leftPan = { x: beam.x1, y: beam.y1 + 74 }
-const rightPan = { x: beam.x2, y: beam.y2 + 96 }
----
-
-
diff --git a/site/src/components/blog/WeightsHero.astro b/site/src/components/blog/WeightsHero.astro
index cb51313d6..de1090fd7 100644
--- a/site/src/components/blog/WeightsHero.astro
+++ b/site/src/components/blog/WeightsHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #13 (exploration 0292). The decorative art lives in
-// WeightsArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import WeightsArt from './WeightsArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/components/blog/WorkshopArt.astro b/site/src/components/blog/WorkshopArt.astro
deleted file mode 100644
index bef3881a7..000000000
--- a/site/src/components/blog/WorkshopArt.astro
+++ /dev/null
@@ -1,190 +0,0 @@
----
-// Decorative art for blog post #10 (exploration 0267), extracted so the same
-// inline SVG backs both the post hero and the blog-index preview card (no
-// external assets — Self-Audit parity). The scene is the essay's title drawn
-// literally: on the left, a walled garden — high brick, clipped topiary showing
-// above the top, a barred gate with no handle on the outside. On the right, an
-// open workshop — a pegboard of hanging tools, a workbench with parts laid out
-// mid-repair, and the cosmic-X glowing overhead as the work lamp. A dotted
-// footpath runs from the gate to the bench: the walk from renting to making.
-interface Props {
- /** Override the wrapper classes; defaults to full-bleed for hero use. */
- class?: string
-}
-const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
- Astro.props
-
-// Brick courses for the garden wall (y of each mortar line).
-const courses = [148, 176, 204, 232, 260, 288]
-// The footpath from the gate out to the workbench.
-const path = [
- [368, 306],
- [420, 298],
- [478, 296],
- [538, 300],
- [598, 296],
- [652, 292]
-]
- .map(([x, y]) => `${x},${y}`)
- .join(' ')
----
-
-
diff --git a/site/src/components/blog/WorkshopHero.astro b/site/src/components/blog/WorkshopHero.astro
index 014e23017..5f0feccdc 100644
--- a/site/src/components/blog/WorkshopHero.astro
+++ b/site/src/components/blog/WorkshopHero.astro
@@ -1,8 +1,5 @@
---
-// Hero band for blog post #10 (exploration 0267). The decorative art lives in
-// WorkshopArt.astro so the same inline SVG backs both this hero and the
-// blog-index preview card; this component layers the post's title/deck over it.
-import WorkshopArt from './WorkshopArt.astro'
+import BlogArt from './BlogArt.astro'
interface Props {
title: string
@@ -15,7 +12,11 @@ const { title, deck, date, readingMinutes, tags } = Astro.props
---
-
+
+
diff --git a/site/src/data/blog-art.ts b/site/src/data/blog-art.ts
index b453ce365..956a15967 100644
--- a/site/src/data/blog-art.ts
+++ b/site/src/data/blog-art.ts
@@ -1,71 +1,63 @@
-/**
- * Slug → hero art for every blog post.
- *
- * Each essay's art-directed hero is an inline SVG `*Art.astro` component. The
- * same component is rendered three ways, so the mapping lives here rather than
- * in any one consumer:
- * - the post's own hero band (`*Hero.astro` imports its Art directly);
- * - the blog-index preview card (`pages/blog/index.astro`);
- * - the post's social-card PNG (`pages/blog/og/[slug].png.ts`), which
- * rasterises the SVG at build time so link unfurls on Slack, X, iMessage
- * and the rest — none of which accept SVG for `og:image` — show the
- * essay's picture instead of the site-wide screenshot.
- *
- * Every post must have an entry: the OG endpoint fails the build for a slug
- * it cannot draw, rather than quietly falling back to the generic image.
- */
-import BallotArt from '../components/blog/BallotArt.astro'
-import BoardArt from '../components/blog/BoardArt.astro'
-import BrickArt from '../components/blog/BrickArt.astro'
-import DisguiseArt from '../components/blog/DisguiseArt.astro'
-import DoorHouseArt from '../components/blog/DoorHouseArt.astro'
-import DustArt from '../components/blog/DustArt.astro'
-import ForestArt from '../components/blog/ForestArt.astro'
-import HookArt from '../components/blog/HookArt.astro'
-import LeverArt from '../components/blog/LeverArt.astro'
-import MycelialArt from '../components/blog/MycelialArt.astro'
-import PirateArt from '../components/blog/PirateArt.astro'
-import StarArt from '../components/blog/StarArt.astro'
-import LoomArt from '../components/blog/LoomArt.astro'
-import TableWallArt from '../components/blog/TableWallArt.astro'
-import TillerArt from '../components/blog/TillerArt.astro'
-import TimeoutArt from '../components/blog/TimeoutArt.astro'
-import VaultArt from '../components/blog/VaultArt.astro'
-import WeightsArt from '../components/blog/WeightsArt.astro'
-import WorkshopArt from '../components/blog/WorkshopArt.astro'
-import RingsArt from '../components/blog/RingsArt.astro'
-import PalimpsestArt from '../components/blog/PalimpsestArt.astro'
-import RecordArt from '../components/blog/RecordArt.astro'
-import HarvestArt from '../components/blog/HarvestArt.astro'
-import MeterArt from '../components/blog/MeterArt.astro'
-import HundredYearArt from '../components/blog/HundredYearArt.astro'
+/** Generated cover artwork shared by post heroes, index cards and social images. */
+export interface BlogArt {
+ src: string
+ srcSet: string
+ alt: string
+ width: number
+ height: number
+}
-export type HeroArtComponent = typeof PirateArt
+const descriptions: Record = {
+ 'atoms-for-the-ballot': 'A hand places a folded paper ballot into a wooden ballot box.',
+ 'the-hundred-year-machine': 'A well-used treadle sewing machine stands in a sunlit room.',
+ 'the-door-inside-the-house':
+ 'A person waits outside a closed door beside a window into their study.',
+ 'the-table-and-the-wall':
+ 'A carpenter works on a wooden table beside an opening onto a street market.',
+ 'the-matchmaker-and-the-meter':
+ 'A friend introduces two people in a lively neighborhood dance hall.',
+ 'the-harvest-you-can-count':
+ 'A measured sheaf of grain and a basket of varied produce share a garden table.',
+ 'rig-the-game-or-play':
+ 'An abandoned board game has most of its houses and coins piled in one corner.',
+ 'the-worlds-greatest-record-store':
+ 'People browse and share records in a warmly lit neighborhood record shop.',
+ palimpsest: 'Faint geometric drawings show through layers of an old parchment manuscript.',
+ 'tree-rings': 'The growth rings of an old fallen tree are surrounded by moss and ferns.',
+ 'people-in-disguise':
+ 'Musicians play distinct acoustic instruments together in a warm wooden room.',
+ 'clutch-power': 'Hands connect colorful construction bricks beside a small model bridge.',
+ 'weights-you-can-hold':
+ 'A hand rests on a portable drive beside a laptop, camera, book and record.',
+ timeout: 'A comfortable chair and blanket wait beside a window overlooking a quiet garden.',
+ 'the-vault-and-the-view':
+ 'Three open windows look onto one garden beside an archive of personal papers.',
+ 'the-workshop-and-the-walled-garden':
+ 'An open workshop with tools and a model on its workbench leads into a garden.',
+ 'hand-on-the-tiller': 'A hand steers a wooden sailboat with its tiller over a calm blue sea.',
+ 'the-tip-of-the-hook':
+ 'A small iceberg peak reveals a much larger translucent body beneath the water.',
+ 'a-great-pirate-age': 'A small sailing ship makes its way between sunlit islands.',
+ 'data-should-work-like-soil': 'A thriving forest above a cutaway of soil, roots and mycelium.',
+ 'the-gentlest-furnace': 'A luminous golden star against a deep blue field of stars.',
+ 'the-right-to-say-no': 'A person carries a box through an open door into a sunlit meadow.',
+ 'the-desert-that-feeds-the-forest':
+ 'A plume of desert dust crosses the ocean toward a green rainforest.',
+ 'the-forest-and-the-field': 'Orderly crop rows meet a diverse forest along a winding path.',
+ 'the-loom-you-can-read':
+ 'A wooden handloom exposes the threads and mechanism that weave its cloth.'
+}
-export const heroArt: Record = {
- 'atoms-for-the-ballot': BallotArt,
- 'the-hundred-year-machine': HundredYearArt,
- 'the-door-inside-the-house': DoorHouseArt,
- 'the-table-and-the-wall': TableWallArt,
- 'the-matchmaker-and-the-meter': MeterArt,
- 'the-harvest-you-can-count': HarvestArt,
- 'rig-the-game-or-play': BoardArt,
- 'the-worlds-greatest-record-store': RecordArt,
- palimpsest: PalimpsestArt,
- 'tree-rings': RingsArt,
- 'people-in-disguise': DisguiseArt,
- 'clutch-power': BrickArt,
- 'weights-you-can-hold': WeightsArt,
- timeout: TimeoutArt,
- 'the-vault-and-the-view': VaultArt,
- 'the-workshop-and-the-walled-garden': WorkshopArt,
- 'hand-on-the-tiller': TillerArt,
- 'the-tip-of-the-hook': HookArt,
- 'a-great-pirate-age': PirateArt,
- 'data-should-work-like-soil': MycelialArt,
- 'the-gentlest-furnace': StarArt,
- 'the-right-to-say-no': LeverArt,
- 'the-desert-that-feeds-the-forest': DustArt,
- 'the-forest-and-the-field': ForestArt,
- 'the-loom-you-can-read': LoomArt
+/** Fail the build when a post has no cover instead of silently hiding it. */
+export const blogArt = (slug: string): BlogArt => {
+ const alt = descriptions[slug]
+ if (!alt) throw new Error(`No blog cover registered for "${slug}"`)
+ const src = `/blog/covers/${slug}.webp`
+ return {
+ src,
+ srcSet: `/blog/covers/${slug}-800.webp 800w, ${src} 1600w`,
+ alt,
+ width: 1600,
+ height: 800
+ }
}
diff --git a/site/src/data/blog.ts b/site/src/data/blog.ts
index 5266dddd8..cb7e689e1 100644
--- a/site/src/data/blog.ts
+++ b/site/src/data/blog.ts
@@ -96,7 +96,7 @@ export interface BlogPost {
readingMinutes: number
/**
* Optional social-card image override (absolute site path or https URL).
- * Unset means the post's hero art, rasterised at `/blog/og/.png`
+ * Unset means the post's cover art, resized at `/blog/og/.png`
* (see `postSocialImage`).
*/
hero?: { src: string; alt: string }
@@ -485,10 +485,9 @@ export function allPosts(): BlogPost[] {
/**
* The image a link unfurler should show for a post (`og:image` / `twitter:image`).
*
- * Defaults to the post's own hero art, rasterised to PNG at build time by
- * `pages/blog/og/[slug].png.ts` — unfurlers do not render SVG, and before this
- * every essay shared the site-wide workbench screenshot. `hero` overrides it
- * for a post whose card should be a photograph or bespoke image instead.
+ * Defaults to the post's generated cover, resized to a social-card PNG at
+ * build time by `pages/blog/og/[slug].png.ts`. A `hero` override can supply
+ * a different image when needed.
*/
export function postSocialImage(post: BlogPost): { src: string; alt: string } {
return post.hero ?? { src: `/blog/og/${post.slug}.png`, alt: `Cover art for “${post.title}”` }
diff --git a/site/src/data/changelog/2026-10-09-every-blog-essay-has-its-own-illustrated.json b/site/src/data/changelog/2026-10-09-every-blog-essay-has-its-own-illustrated.json
new file mode 100644
index 000000000..1d1a4fac9
--- /dev/null
+++ b/site/src/data/changelog/2026-10-09-every-blog-essay-has-its-own-illustrated.json
@@ -0,0 +1,8 @@
+{
+ "id": "2026-10-09-every-blog-essay-has-its-own-illustrated",
+ "date": "October 9, 2026",
+ "title": "Every blog essay has its own illustrated cover",
+ "summary": "New artwork brings each essay to life on the blog, inside posts, and in shared links.",
+ "highlights": [],
+ "tags": ["platform"]
+}
diff --git a/site/src/pages/blog/index.astro b/site/src/pages/blog/index.astro
index 042ac1f41..744ac5dd0 100644
--- a/site/src/pages/blog/index.astro
+++ b/site/src/pages/blog/index.astro
@@ -2,7 +2,7 @@
import Base from '../../layouts/Base.astro'
import Nav from '../../components/sections/Nav.astro'
import Footer from '../../components/sections/Footer.astro'
-import { heroArt } from '../../data/blog-art'
+import BlogArt from '../../components/blog/BlogArt.astro'
import Byline from '../../components/blog/Byline.astro'
import { publishedPosts, formatPostDate } from '../../data/blog'
@@ -45,18 +45,19 @@ const posts = publishedPosts()
) : (
{posts.map((post) => {
- const Art = heroArt[post.slug]
return (