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) +--- + +{art.alt} 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() ) : (