scriptorium 0.0.3 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/icons/social/reddit.png +0 -0
  7. data/assets/icons/social/x-logo.png +0 -0
  8. data/assets/icons/ui/.DS_Store +0 -0
  9. data/assets/icons/ui/back.png +0 -0
  10. data/assets/icons/ui/copy.png +0 -0
  11. data/assets/icons/ui/down.png +0 -0
  12. data/assets/icons/ui/end.png +0 -0
  13. data/assets/icons/ui/exit.png +0 -0
  14. data/assets/icons/ui/foo +10 -0
  15. data/assets/icons/ui/home.png +0 -0
  16. data/assets/icons/ui/left.png +0 -0
  17. data/assets/icons/ui/next.png +0 -0
  18. data/assets/icons/ui/right.png +0 -0
  19. data/assets/icons/ui/start.png +0 -0
  20. data/assets/icons/ui/up.png +0 -0
  21. data/assets/imagenotfound.jpg +0 -0
  22. data/assets/samples/placeholder.svg +9 -0
  23. data/assets/themes/standard/favicon.svg +6 -0
  24. data/bin/sblog +84 -5
  25. data/bin/scriptorium +1 -0
  26. data/doc/README.txt +6 -0
  27. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +94 -0
  28. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +2 -0
  29. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +45 -0
  30. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  31. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  32. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  33. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +35 -0
  34. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +27 -0
  35. data/doc/anti-amnesia/20250807-213025.md +116 -0
  36. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  37. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  38. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  39. data/doc/banner_svg_config.md +114 -0
  40. data/doc/contrib.lt3 +8 -0
  41. data/doc/dependencies.md +281 -0
  42. data/doc/hacker.lt3 +5 -0
  43. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  44. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  45. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  46. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  47. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  48. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  49. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  50. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  51. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  52. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  53. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  54. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  55. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  56. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  57. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  58. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  59. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  60. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  61. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  62. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  63. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  64. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  65. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  66. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  67. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  68. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  69. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  70. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  71. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  72. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  73. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  74. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  75. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  76. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  77. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  78. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  79. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  80. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  81. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  82. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  83. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  84. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  85. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  86. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  87. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  88. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  89. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  90. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  91. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  92. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  93. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  94. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  95. data/doc/imported/0018-ruby-puns/post.html +46 -0
  96. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  97. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  98. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  99. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  100. data/doc/imported/0020-fffff/metadata.txt +7 -0
  101. data/doc/imported/0020-fffff/post.html +24 -0
  102. data/doc/imported/0020-fffff/source.lt3 +12 -0
  103. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  104. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  105. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  106. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  107. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  108. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  109. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  110. data/doc/imported/0024-just-a-test/post.html +24 -0
  111. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  112. data/doc/imported/import_summary.txt +98 -0
  113. data/doc/livetext-informal-spec.txt +65 -0
  114. data/doc/myuserdoc/ch-0.lt3 +31 -0
  115. data/doc/myuserdoc/ch-1.lt3 +37 -0
  116. data/doc/myuserdoc/ch-10.lt3 +22 -0
  117. data/doc/myuserdoc/ch-2.lt3 +37 -0
  118. data/doc/myuserdoc/ch-3.lt3 +19 -0
  119. data/doc/myuserdoc/ch-4.lt3 +43 -0
  120. data/doc/myuserdoc/ch-5.lt3 +22 -0
  121. data/doc/myuserdoc/ch-6.lt3 +19 -0
  122. data/doc/myuserdoc/ch-7.lt3 +16 -0
  123. data/doc/myuserdoc/ch-8.lt3 +13 -0
  124. data/doc/myuserdoc/ch-9.lt3 +19 -0
  125. data/doc/myuserdoc/tweak.rb +18 -0
  126. data/doc/myuserdoc/userdoc-toc.txt +88 -0
  127. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  128. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  129. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  130. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  131. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  132. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  133. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  134. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  135. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  136. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  137. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  138. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  139. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  140. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  141. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  142. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  143. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  144. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  145. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  146. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  147. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  148. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  149. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  150. data/doc/old-repo +1 -0
  151. data/doc/reddit_credentials_template.json +8 -0
  152. data/doc/reddit_integration.md +207 -0
  153. data/doc/user.lt3 +35 -0
  154. data/doc/user_guide_section_1.md +137 -0
  155. data/doc/user_guide_section_10.md +515 -0
  156. data/doc/user_guide_section_11.md +708 -0
  157. data/doc/user_guide_section_2.md +233 -0
  158. data/doc/user_guide_section_3.md +5 -0
  159. data/doc/user_guide_section_4.md +221 -0
  160. data/doc/user_guide_section_5.md +243 -0
  161. data/doc/user_guide_section_6.md +147 -0
  162. data/doc/user_guide_section_7.md +311 -0
  163. data/doc/user_guide_section_8.md +224 -0
  164. data/doc/user_guide_section_9.md +375 -0
  165. data/lib/rouge/lexers/livetext.rb +74 -0
  166. data/lib/scriptorium/api.rb +2373 -0
  167. data/lib/scriptorium/banner_svg.rb +729 -0
  168. data/lib/scriptorium/contract.rb +34 -0
  169. data/lib/scriptorium/exceptions.rb +201 -1
  170. data/lib/scriptorium/helpers.rb +675 -0
  171. data/lib/scriptorium/post.rb +259 -0
  172. data/lib/scriptorium/reddit.rb +83 -0
  173. data/lib/scriptorium/repo.rb +938 -0
  174. data/lib/scriptorium/standard_files.rb +149 -0
  175. data/lib/scriptorium/support/bootstrap/css.txt +5 -0
  176. data/lib/scriptorium/support/bootstrap/js.txt +4 -0
  177. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  178. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  179. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  180. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  181. data/lib/scriptorium/support/config/reddit.txt +10 -0
  182. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  183. data/lib/scriptorium/support/config/social.txt +8 -0
  184. data/lib/scriptorium/support/highlight/css.txt +2 -0
  185. data/lib/scriptorium/support/highlight/custom.css +119 -0
  186. data/lib/scriptorium/support/highlight/js.txt +1 -0
  187. data/lib/scriptorium/support/post_index/config.txt +15 -0
  188. data/lib/scriptorium/support/post_index/style.css +55 -0
  189. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  190. data/lib/scriptorium/support/templates/initial_post.lt3 +12 -0
  191. data/lib/scriptorium/support/templates/layout.txt +5 -0
  192. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  193. data/lib/scriptorium/support/theme/footer.lt3 +2 -0
  194. data/lib/scriptorium/support/theme/header.lt3 +4 -0
  195. data/lib/scriptorium/support/theme/left.lt3 +3 -0
  196. data/lib/scriptorium/support/theme/main.lt3 +5 -0
  197. data/lib/scriptorium/support/theme/right.lt3 +3 -0
  198. data/lib/scriptorium/theme.rb +192 -0
  199. data/lib/scriptorium/version.rb +1 -1
  200. data/lib/scriptorium/view.rb +1021 -0
  201. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  202. data/lib/scriptorium/widgets/links.rb +112 -0
  203. data/lib/scriptorium/widgets/pages.rb +133 -0
  204. data/lib/scriptorium/widgets/widget.rb +133 -0
  205. data/lib/scriptorium.rb +38 -34
  206. data/lib/skeleton.rb +10 -1
  207. data/scriptorium.gemspec +17 -5
  208. data/test/README.md +69 -0
  209. data/test/WEB_INTEGRATION_README.md +196 -0
  210. data/test/all +83 -0
  211. data/test/api_demo.rb +99 -0
  212. data/test/assets/imagenotfound.jpg +0 -0
  213. data/test/assets/images/.DS_Store +0 -0
  214. data/test/assets/images/README.md +27 -0
  215. data/test/assets/images/odd_aspect.png +0 -0
  216. data/test/assets/images/perfect.png +0 -0
  217. data/test/assets/images/small.png +0 -0
  218. data/test/assets/images/tall.png +0 -0
  219. data/test/assets/images/very_tall.png +0 -0
  220. data/test/assets/images/very_wide.png +0 -0
  221. data/test/assets/images/wide.png +0 -0
  222. data/test/assets/testbanner.jpg +0 -0
  223. data/test/banner_svg/simple_helpers.rb +13 -0
  224. data/test/banner_svg/unit.rb +1000 -0
  225. data/test/config/deployment.txt +5 -0
  226. data/test/ed_test.rb +204 -0
  227. data/test/integration/cursor_banner_combinations.rb +193 -0
  228. data/test/integration/cursor_banner_features.rb +374 -0
  229. data/test/integration/integration_test.rb +326 -0
  230. data/test/integration/preview_flow_test.rb +94 -0
  231. data/test/livetext_plugin_test.rb +500 -0
  232. data/test/manual/asset_mgmt.rb +67 -0
  233. data/test/manual/banner-tests/index.html +45 -0
  234. data/test/manual/banner-tests/svg.txt +3 -0
  235. data/test/manual/banner-tests/test01.html +122 -0
  236. data/test/manual/banner-tests/test02.html +122 -0
  237. data/test/manual/banner-tests/test03.html +122 -0
  238. data/test/manual/banner-tests/test04.html +129 -0
  239. data/test/manual/banner-tests/test05.html +129 -0
  240. data/test/manual/banner-tests/test06.html +129 -0
  241. data/test/manual/banner-tests/test07.html +129 -0
  242. data/test/manual/banner-tests/test08.html +123 -0
  243. data/test/manual/banner-tests/test09.html +123 -0
  244. data/test/manual/banner-tests/test10.html +123 -0
  245. data/test/manual/banner-tests/test11.html +123 -0
  246. data/test/manual/banner-tests/test12.html +123 -0
  247. data/test/manual/banner-tests/test13.html +123 -0
  248. data/test/manual/banner-tests/test14.html +123 -0
  249. data/test/manual/banner-tests/test15.html +122 -0
  250. data/test/manual/banner-tests/test16.html +122 -0
  251. data/test/manual/banner-tests/test17.html +122 -0
  252. data/test/manual/banner-tests/test18.html +132 -0
  253. data/test/manual/banner-tests/test19.html +132 -0
  254. data/test/manual/banner-tests/test20.html +132 -0
  255. data/test/manual/banner-tests/test21.html +132 -0
  256. data/test/manual/banner-tests/test22.html +132 -0
  257. data/test/manual/banner-tests/test23.html +132 -0
  258. data/test/manual/banner-tests/test24.html +132 -0
  259. data/test/manual/banner-tests/test25.html +131 -0
  260. data/test/manual/banner_environment.rb +205 -0
  261. data/test/manual/codemirror_demo.html +773 -0
  262. data/test/manual/create_posts_for_web.rb +114 -0
  263. data/test/manual/environment.rb +67 -0
  264. data/test/manual/make_banner.rb +153 -0
  265. data/test/manual/preview_manual_test.rb +129 -0
  266. data/test/manual/sample_banner_config.txt +12 -0
  267. data/test/manual/test_advanced_widgets.rb +73 -0
  268. data/test/manual/test_banner_combinations.rb +120 -0
  269. data/test/manual/test_banner_features.rb +306 -0
  270. data/test/manual/test_banner_integration.rb +115 -0
  271. data/test/manual/test_banner_radial.rb +87 -0
  272. data/test/manual/test_basic_posts.rb +47 -0
  273. data/test/manual/test_layout_widgets.rb +40 -0
  274. data/test/manual/test_pagination.rb +24 -0
  275. data/test/manual/test_random_posts.rb +38 -0
  276. data/test/manual/test_syntax_highlighting.rb +167 -0
  277. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  278. data/test/rubytext/rubytext_demo_test.rb +42 -0
  279. data/test/rubytext/rubytext_testing_guide.md +277 -0
  280. data/test/run_automated_tests.rb +45 -0
  281. data/test/staging/.DS_Store +0 -0
  282. data/test/support/preview_utils.rb +88 -0
  283. data/test/syntax_highlighting_test.lt3 +124 -0
  284. data/test/test_gem_assets.rb +48 -0
  285. data/test/test_helpers.rb +240 -0
  286. data/test/tui_editor_integration_test.rb +296 -0
  287. data/test/tui_integration_test.rb +883 -0
  288. data/test/unit/api.rb +1776 -0
  289. data/test/unit/asset_management.rb +219 -0
  290. data/test/unit/backup_test.rb +451 -0
  291. data/test/unit/clipboard_test.rb +60 -0
  292. data/test/unit/contract_test.rb +69 -0
  293. data/test/unit/core.rb +1211 -0
  294. data/test/unit/deploy_config_test.rb +248 -0
  295. data/test/unit/deploy_test.rb +478 -0
  296. data/test/unit/edit_post_test.rb +168 -0
  297. data/test/unit/gem_asset_management.rb +183 -0
  298. data/test/unit/livetext_basic.rb +57 -0
  299. data/test/unit/livetext_compatibility.rb +82 -0
  300. data/test/unit/parse_cmd_test.rb +260 -0
  301. data/test/unit/permalink_copy_test.rb +211 -0
  302. data/test/unit/post.rb +309 -0
  303. data/test/unit/post_index_config_test.rb +258 -0
  304. data/test/unit/post_state_helpers_test.rb +137 -0
  305. data/test/unit/read_commented_file_test.rb +278 -0
  306. data/test/unit/reddit_test.rb +235 -0
  307. data/test/unit/repo.rb +569 -0
  308. data/test/unit/social_test.rb +366 -0
  309. data/test/unit/syntax_highlighting.rb +70 -0
  310. data/test/unit/theme_management_test.rb +91 -0
  311. data/test/unit/view.rb +498 -0
  312. data/test/unit/widgets.rb +669 -0
  313. data/test/web_integration_test.rb +231 -0
  314. data/test/web_test_helper.rb +218 -0
  315. data/test/web_workflow_test.rb +527 -0
  316. data/test/wizard_test.rb +123 -0
  317. data/ui/README.md +67 -0
  318. data/ui/common/lib/ui_common.rb +8 -0
  319. data/ui/rubytext/README.md +191 -0
  320. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  321. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  322. data/ui/tui/bin/scriptorium +1890 -0
  323. data/ui/tui/test/tui_test.rb +23 -0
  324. data/ui/web/app/app.rb +2600 -0
  325. data/ui/web/app/assets/livetext_mode.js +244 -0
  326. data/ui/web/app/error_helpers.rb +150 -0
  327. data/ui/web/app/views/advanced_config.erb +196 -0
  328. data/ui/web/app/views/asset_management.erb +645 -0
  329. data/ui/web/app/views/backup_management.erb +238 -0
  330. data/ui/web/app/views/banner_config.erb +200 -0
  331. data/ui/web/app/views/config_widget.erb +232 -0
  332. data/ui/web/app/views/configure_view.erb +401 -0
  333. data/ui/web/app/views/dashboard.erb +154 -0
  334. data/ui/web/app/views/deploy_config.erb +149 -0
  335. data/ui/web/app/views/edit_pages.erb +363 -0
  336. data/ui/web/app/views/edit_post.erb +175 -0
  337. data/ui/web/app/views/edit_theme.erb +73 -0
  338. data/ui/web/app/views/edit_theme_file.erb +74 -0
  339. data/ui/web/app/views/error_page.erb +29 -0
  340. data/ui/web/app/views/header_config.erb +155 -0
  341. data/ui/web/app/views/layout_config.erb +147 -0
  342. data/ui/web/app/views/navbar_config.erb +411 -0
  343. data/ui/web/app/views/theme_management.erb +130 -0
  344. data/ui/web/app/views/view_dashboard.erb +779 -0
  345. data/ui/web/app/views/widgets.erb +249 -0
  346. data/ui/web/bin/scriptorium-web +164 -0
  347. data/ui/web/test/web_basic_test.rb +38 -0
  348. data/ui/web/test_navbar.txt +7 -0
  349. data/ui/web/tmp/timing.log +17 -0
  350. data/ui/web/tmp/web_server.log +0 -0
  351. metadata +434 -8
  352. data/lib/scriptorium/engine.rb +0 -22
  353. data/test/engine/unit.rb +0 -44
@@ -0,0 +1,114 @@
1
+ require_relative './environment'
2
+
3
+ manual_setup
4
+
5
+ # Create a view for testing posts in the correct location for the web app
6
+ # First, clean up the old repo and create a new one in the right location
7
+ system("rm -rf ui/web/scriptorium-TEST")
8
+ @repo = Scriptorium::Repo.create("ui/web/scriptorium-TEST", testmode: true)
9
+ view = @repo.create_view("web-test", "Web App Test View", "Testing posts in the web interface")
10
+
11
+ puts "Creating posts for web app testing..."
12
+
13
+ # Create 45 posts with varied content
14
+ 45.times do |i|
15
+ # Create posts with different titles and content
16
+ title = case i % 5
17
+ when 0
18
+ "Technical Post #{i + 1}: Ruby Programming"
19
+ when 1
20
+ "Creative Post #{i + 1}: Writing and Art"
21
+ when 2
22
+ "Science Post #{i + 1}: Physics and Math"
23
+ when 3
24
+ "Philosophy Post #{i + 1}: Deep Thoughts"
25
+ else
26
+ "Misc Post #{i + 1}: Random Topics"
27
+ end
28
+
29
+ # Create varied body content
30
+ body = case i % 4
31
+ when 0
32
+ "This is a technical post about programming and software development. " +
33
+ "It covers various aspects of Ruby programming, including best practices, " +
34
+ "design patterns, and performance optimization techniques."
35
+ when 1
36
+ "A creative exploration of writing, art, and expression. " +
37
+ "This post delves into the creative process, inspiration, and " +
38
+ "the intersection of technology and creativity."
39
+ when 2
40
+ "An exploration of scientific concepts, from physics to mathematics. " +
41
+ "This post discusses fundamental principles, recent discoveries, " +
42
+ "and the beauty of scientific understanding."
43
+ else
44
+ "Philosophical musings on life, technology, and human nature. " +
45
+ "This post explores deep questions about consciousness, existence, " +
46
+ "and the meaning of it all."
47
+ end
48
+
49
+ # Add some code examples for syntax highlighting
50
+ if i % 3 == 0
51
+ body += "\n\n```ruby\n# Example Ruby code\ndef hello_world\n puts 'Hello, World!'\nend\n\nhello_world\n```"
52
+ end
53
+
54
+ # Create the post
55
+ post = @repo.create_post(title: title, body: body)
56
+
57
+ # Set publication dates spread across different days
58
+ # Use modulo to get valid dates (1-31 for January)
59
+ day = (i % 31) + 1
60
+ post.set_pubdate_with_seconds("2025-01-#{day.to_s.rjust(2, '0')}", i % 60)
61
+
62
+ # Generate the post
63
+ @repo.generate_post(post.id)
64
+
65
+ puts "Created post #{i + 1}: #{title}"
66
+ end
67
+
68
+ # Generate the front page for the view
69
+ view.generate_front_page
70
+
71
+ puts "\n" + "="*60
72
+ puts "POSTS CREATED SUCCESSFULLY!"
73
+ puts "="*60
74
+ puts "Created 45 posts in view 'web-test'"
75
+ puts "Posts have varied titles, content, and publication dates"
76
+ puts "Some posts include code examples for syntax highlighting"
77
+ puts "\nTo view in web app:"
78
+ puts "1. Start the web app: ./ui/web/bin/scriptorium-web start"
79
+ puts "2. Open browser to: http://localhost:4567"
80
+ puts "3. Select the 'web-test' view"
81
+ puts "4. Browse posts and test pagination"
82
+ puts "\nFiles generated in: test/scriptorium-TEST/views/web-test/output/"
83
+
84
+ instruct <<~EOS
85
+ Posts created successfully!
86
+
87
+ Next steps:
88
+ 1. Start the web app with: ./ui/web/bin/scriptorium-web start
89
+ 2. Open browser to http://localhost:4567
90
+ 3. Select 'web-test' view from the dropdown
91
+ 4. Browse the posts and test the interface
92
+
93
+ The posts include:
94
+ - 45 posts with varied content types
95
+ - Different publication dates (spread across 45 days)
96
+ - Code examples for syntax highlighting
97
+ - Varied titles and content themes
98
+
99
+ EOS
100
+
101
+ # Clean up webrick if it was started
102
+ begin
103
+ line = `ps | grep "ruby -run -e httpd" | grep -v grep`
104
+ if line.strip != ""
105
+ pid = line.split.first
106
+ pid2 = pid.to_i + 1
107
+ system("kill #{pid} #{pid2}")
108
+ puts "Cleaned up webrick server"
109
+ end
110
+ rescue => e
111
+ puts "No webrick server to clean up"
112
+ end
113
+
114
+ puts "\nManual test complete! You can now test the web app with these posts."
@@ -0,0 +1,67 @@
1
+ # Set up environment for manual tests
2
+ ENV['PATH'] = "#{ENV['HOME']}/.rbenv/shims:#{ENV['PATH']}"
3
+
4
+ # rbenv should be enabled in the shell before running manual tests
5
+
6
+ require_relative '../../lib/scriptorium'
7
+ require_relative '../test_helpers'
8
+
9
+ include TestHelpers
10
+
11
+ def manual_setup
12
+ system("rm -rf test/scriptorium-TEST")
13
+
14
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true) # true for testing mode
15
+
16
+ @pid = nil
17
+ Dir.chdir(File.expand_path("..", __dir__)) do
18
+ Process.spawn ("ruby -run -e httpd . -p 8000 >/dev/null 2>&1")
19
+ sleep 1
20
+ puts "webrick started\n "
21
+ end
22
+ end
23
+
24
+ def instruct(msg)
25
+ lines = msg.split("\n")
26
+ longest = lines.map {|line| line.length }.max
27
+ puts " +" + "-" * (longest + 4) + "+"
28
+ lines.each do |line|
29
+ puts " | #{line.ljust(longest + 2)} |"
30
+ end
31
+ puts " +" + "-" * (longest + 4) + "+"
32
+ puts
33
+ end
34
+
35
+ def examine(view)
36
+ view = @repo.lookup_view(view)
37
+ index_url = "http://127.0.0.1:8000/scriptorium-TEST/views/#{view.name}/output/index.html"
38
+ puts "Generated front page located at: \n#{index_url}"
39
+
40
+ if ARGV.include?('--automated')
41
+ # Automated mode - just validate files were created
42
+ index_file = "test/scriptorium-TEST/views/#{view.name}/output/index.html"
43
+ if File.exist?(index_file)
44
+ puts "✓ Files generated successfully"
45
+ else
46
+ puts "✗ Error: Index file not generated"
47
+ exit 1
48
+ end
49
+ return
50
+ end
51
+
52
+ puts "Press Enter to open the generated front page to inspect the result."
53
+ STDIN.gets
54
+ cmd = "open #{index_url}"
55
+ see("cmd = ", cmd)
56
+ system("open #{index_url}")
57
+
58
+ puts "Press Enter to kill webrick"
59
+ STDIN.gets
60
+ line = `ps | grep "ruby -run -e httpd" | grep -v grep`
61
+ pid = line.split.first
62
+ puts "line: #{line}"
63
+ puts "pid: #{pid}"
64
+ pid2 = pid.to_i + 1
65
+ system("kill #{pid} #{pid2}")
66
+ puts "Killed\n "
67
+ end
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../../lib/scriptorium'
4
+ include Scriptorium::Helpers
5
+
6
+ # Simple banner test from configuration file
7
+ def test_banner_from_file(config_file, title = nil, subtitle = nil, output_file = nil)
8
+ # Default output file if not specified
9
+ output_file ||= "banner_output.html"
10
+
11
+ # Default titles if not specified
12
+ title ||= "Test Banner"
13
+ subtitle ||= "Generated from config file"
14
+
15
+ # Read the configuration file
16
+ unless File.exist?(config_file)
17
+ puts "Error: Configuration file '#{config_file}' not found"
18
+ exit 1
19
+ end
20
+
21
+ # Use the read_commented_file helper to read the config
22
+ config_lines = read_commented_file(config_file)
23
+ config_content = File.read(config_file) # Keep original for display
24
+
25
+ puts "Read configuration from: #{config_file}"
26
+ puts "Found #{config_lines.length} configuration lines (comments removed)"
27
+ puts "Title: #{title}"
28
+ puts "Subtitle: #{subtitle}"
29
+
30
+ # Create banner with the actual titles from View
31
+ banner = Scriptorium::BannerSVG.new(title, subtitle)
32
+
33
+ # Write config to temporary file for banner to read
34
+ temp_config = "config.txt"
35
+ File.write(temp_config, config_content)
36
+
37
+ # Parse and generate the banner
38
+ banner.parse_header_svg
39
+ svg_output = banner.generate_svg
40
+
41
+ # Clean up temp config file
42
+ File.delete(temp_config) if File.exist?(temp_config)
43
+
44
+ # Create HTML output
45
+ html_content = <<~HTML
46
+ <!DOCTYPE html>
47
+ <html>
48
+ <head>
49
+ <title>Banner Test - #{File.basename(config_file)}</title>
50
+ <style>
51
+ body {
52
+ font-family: Arial, sans-serif;
53
+ margin: 0;
54
+ padding: 20px;
55
+ background: #f5f5f5;
56
+ }
57
+ .banner {
58
+ width: 100%;
59
+ max-width: 800px;
60
+ margin: 0 auto 20px auto;
61
+ border: 2px solid #007cba;
62
+ border-radius: 8px;
63
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
64
+ }
65
+ .config {
66
+ background: white;
67
+ padding: 20px;
68
+ margin: 20px auto;
69
+ max-width: 800px;
70
+ border-radius: 8px;
71
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
72
+ }
73
+ .config h3 {
74
+ margin-top: 0;
75
+ color: #333;
76
+ }
77
+ .config pre {
78
+ background: #f8f9fa;
79
+ padding: 15px;
80
+ border-radius: 4px;
81
+ overflow-x: auto;
82
+ font-family: monospace;
83
+ font-size: 12px;
84
+ }
85
+ .info {
86
+ background: white;
87
+ padding: 20px;
88
+ margin: 20px auto;
89
+ max-width: 800px;
90
+ border-radius: 8px;
91
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
92
+ }
93
+ .processed-config {
94
+ background: #e8f4fd;
95
+ padding: 15px;
96
+ margin: 10px 0;
97
+ border-radius: 4px;
98
+ font-family: monospace;
99
+ font-size: 12px;
100
+ }
101
+ </style>
102
+ </head>
103
+ <body>
104
+ <div class="banner">
105
+ #{svg_output}
106
+ </div>
107
+
108
+ <div class="info">
109
+ <h3>Banner Information</h3>
110
+ <p><strong>Configuration file:</strong> #{config_file}</p>
111
+ <p><strong>Output file:</strong> #{output_file}</p>
112
+ <p><strong>Generated:</strong> #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}</p>
113
+ <p><strong>Configuration lines processed:</strong> #{config_lines.length}</p>
114
+ </div>
115
+
116
+ <div class="config">
117
+ <h3>Configuration Used </h3>
118
+ <pre>#{config_content}</pre>
119
+ </div>
120
+ </body>
121
+ </html>
122
+ HTML
123
+
124
+ # Write the HTML file
125
+ File.write(output_file, html_content)
126
+ puts "Generated banner HTML: #{output_file}"
127
+
128
+ # Open in browser if on macOS
129
+ if RUBY_PLATFORM.include?('darwin')
130
+ puts "Opening in browser..."
131
+ system("open #{output_file}")
132
+ else
133
+ puts "Open #{output_file} in your browser to view the banner"
134
+ end
135
+ end
136
+
137
+ # Main execution
138
+ if ARGV.empty?
139
+ puts "Usage: ruby make_banner.rb <config_file> [title] [subtitle] [output_file]"
140
+ puts ""
141
+ puts "Example:"
142
+ puts " ruby make_banner.rb my_banner_config.txt"
143
+ puts " ruby make_banner.rb my_banner_config.txt 'My Blog' 'A subtitle'"
144
+ puts " ruby make_banner.rb my_banner_config.txt 'My Blog' 'A subtitle' custom_output.html"
145
+ exit 1
146
+ end
147
+
148
+ config_file = ARGV[0]
149
+ title = ARGV[1] # Optional
150
+ subtitle = ARGV[2] # Optional
151
+ output_file = ARGV[3] # Optional
152
+
153
+ test_banner_from_file(config_file, title, subtitle, output_file)
@@ -0,0 +1,129 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/scriptorium'
3
+ require_relative '../test_helpers'
4
+ require_relative '../web_test_helper'
5
+ require_relative '../support/preview_utils'
6
+ require 'fileutils'
7
+ require 'shellwords'
8
+
9
+ # Manual-inspection oriented preview tests
10
+ # - Does not enforce strict order
11
+ # - Prints absolute paths and preview URLs to inspect
12
+ # - Set MANUAL_KEEP=1 to skip teardown so artifacts remain
13
+
14
+ class PreviewManualTest < Minitest::Test
15
+ include TestHelpers
16
+ include WebTestHelper
17
+
18
+ def interactive_open(label:, paths: [], urls: [])
19
+ PreviewUtils.interactive_open(label: label, paths: paths, urls: urls)
20
+ end
21
+
22
+ def setup
23
+ # Use the web app's test repo location so preview routes see generated files
24
+ @repo_name = "ui/web/scriptorium-TEST"
25
+ @view_name = "manual_preview_view"
26
+
27
+ @api = Scriptorium::API.new
28
+ # Ensure a clean slate before creating the repo
29
+ begin
30
+ FileUtils.rm_rf(@repo_name)
31
+ backup_dir = File.join(File.dirname(@repo_name), 'backup-scriptorium-TEST')
32
+ FileUtils.rm_rf(backup_dir)
33
+ rescue => e
34
+ puts "[Manual] Warning: cleanup failed: #{e.message}"
35
+ end
36
+ @api.create_repo(@repo_name)
37
+ @api.open_repo(@repo_name)
38
+ @api.create_view(@view_name, "Manual Preview View")
39
+
40
+ # Ensure required UI/social icons are available in the view
41
+ begin
42
+ @api.copy_asset('icons/ui/back.png', from: 'gem', to: 'view', view: @view_name)
43
+ rescue => e
44
+ puts "[Manual] Warning: could not copy icons/ui/back.png from gem: #{e.message}"
45
+ end
46
+ begin
47
+ @api.copy_asset('icons/social/reddit.png', from: 'gem', to: 'view', view: @view_name)
48
+ rescue => e
49
+ puts "[Manual] Warning: could not copy icons/social/reddit.png from gem: #{e.message}"
50
+ end
51
+
52
+ # Seed some posts
53
+ @api.create_post("Manual Post A", "Blurb A")
54
+ @api.create_post("Manual Post B", "Blurb B")
55
+
56
+ # Configure post index (line break in date, enable pagination)
57
+ PreviewUtils.write_post_index_config(@repo_name, @view_name)
58
+
59
+ # Add an image and reference it in post 0001
60
+ # Use a real image so the browser can render it
61
+ sample_image = File.expand_path("test/assets/testbanner.jpg")
62
+ @api.upload_asset(sample_image, filename: 'manual.jpg')
63
+ PreviewUtils.reference_image_in_post(@repo_name, '0001', 'manual.jpg')
64
+
65
+ @api.generate_view(@view_name)
66
+ end
67
+
68
+ def teardown
69
+ return if ENV['MANUAL_KEEP'] == '1'
70
+ FileUtils.rm_rf(@repo_name)
71
+ end
72
+
73
+ # 100-series: Index preview
74
+ def test_100_preview_index_artifacts
75
+ base = File.expand_path(File.join(@repo_name, 'views', @view_name))
76
+ index_file = File.join(base, 'output', 'index.html')
77
+ post_index = File.join(base, 'output', 'post_index.html')
78
+ page1 = File.join(base, 'output', 'page1.html')
79
+
80
+ assert File.exist?(index_file), 'index.html missing'
81
+ assert File.exist?(post_index), 'post_index.html missing'
82
+ assert File.exist?(page1), 'page1.html missing'
83
+
84
+ puts "\n[Manual] Inspect artifacts (server-first). Files shown for reference:"
85
+ puts " URL : http://localhost:4567/preview/#{@view_name}/index.html"
86
+ puts " File: #{index_file}"
87
+ puts " File: #{post_index}"
88
+ puts " File: #{page1}"
89
+ # Restart server to ensure preview routes work
90
+ stop_web_server
91
+ start_web_server
92
+ # Open server URL only to avoid file:// CORS issues
93
+ interactive_open(label: 'index (server)', paths: [], urls: ["http://localhost:4567/preview/#{@view_name}/index.html"])
94
+ end
95
+
96
+ # 200-series: One post page and asset
97
+ def test_200_post_and_asset_reference
98
+ base = File.expand_path(File.join(@repo_name, 'views', @view_name))
99
+ meta = File.read(File.join(@repo_name, 'posts', '0001', 'meta.txt'))
100
+ slug = meta.lines.grep(/^post.slug\s+/).first.split.last
101
+ post_html = File.join(base, 'output', 'posts', slug)
102
+ asset_in_output = File.join(base, 'output', 'assets', 'manual.jpg')
103
+
104
+ assert File.exist?(post_html), 'Post HTML missing'
105
+ html = File.read(post_html)
106
+ assert_includes html, "<img src=../assets/manual.jpg", 'Expected relative asset path in post'
107
+
108
+ # Global asset copied to output for serving
109
+ assert File.exist?(asset_in_output), 'Expected asset copied to output/assets'
110
+
111
+ # Clarify exact image path and how the browser resolves it
112
+ img_src = html[/<img[^>]+src=([^\s>]+)/, 1]
113
+ resolved_img_path = File.expand_path(img_src, File.dirname(post_html)) if img_src
114
+
115
+ puts "\n[Manual] Inspect post and asset:"
116
+ puts " File: #{post_html}"
117
+ puts " File: #{asset_in_output}"
118
+ puts " IMG src (from HTML): #{img_src}"
119
+ puts " IMG resolves to : #{resolved_img_path}"
120
+ puts " URL : /preview/#{@view_name}/posts/#{slug}"
121
+ puts " URL : /preview/#{@view_name}/assets/manual.jpg"
122
+ # Restart server to ensure preview routes work
123
+ stop_web_server
124
+ start_web_server
125
+ # Open server URLs only; list local paths for reference
126
+ interactive_open(label: 'post (server)', paths: [], urls: ["http://localhost:4567/preview/#{@view_name}/posts/#{slug}"])
127
+ interactive_open(label: 'asset (server)', paths: [], urls: ["http://localhost:4567/preview/#{@view_name}/assets/manual.jpg"])
128
+ end
129
+ end
@@ -0,0 +1,12 @@
1
+ aspect 7.0 # Aspect ratio (width:height)
2
+ text.font verdana # For both title and subtitle
3
+ text.color #cccccc
4
+ text.justify left # For both title and subtitle
5
+
6
+ title.scale 0.8 # ~48px (1.5x <h1>)
7
+ subtitle.scale 0.4 # 24px ~= <h2>
8
+
9
+ title.style bold # boldfaced title
10
+ subtitle.style bold italic # bold *and* italic (actually uses SVG weight and style)
11
+ title.xy 5 10
12
+ subtitle.xy 5 40
@@ -0,0 +1,73 @@
1
+ require_relative './environment'
2
+
3
+ manual_setup
4
+
5
+ srand(42) # for random posts
6
+
7
+ create_3_views
8
+ create_13_posts_manual
9
+ alter_pubdates
10
+
11
+ view = @repo.view("blog3")
12
+ File.open(view.dir/:config/"layout.txt", "w") do |f|
13
+ f.puts "header"
14
+ f.puts "main"
15
+ f.puts "right 20%"
16
+ end
17
+ view.generate_empty_containers
18
+ File.open(view.dir/:config/"right.txt", "w") do |f|
19
+ f.puts "widget links"
20
+ f.puts "widget pages"
21
+ end
22
+
23
+ # Set up the Links widget
24
+ data = view.dir/:widgets/"links/list.txt"
25
+ Dir.mkdir(view.dir/:widgets/"links")
26
+ File.open(data, "w") do |f|
27
+ f.puts "https://www.google.com,Google"
28
+ f.puts "https://www.yahoo.com,Yahoo"
29
+ end
30
+ see_file data
31
+
32
+ # Set up the Pages widget with two dummy pages
33
+ pages_dir = view.dir/:widgets/"pages"
34
+ Dir.mkdir(pages_dir)
35
+
36
+ # Create the list.txt file that the Pages widget reads
37
+ File.open(pages_dir/"list.txt", "w") do |f|
38
+ f.puts "about"
39
+ f.puts "contact"
40
+ end
41
+
42
+ # Create the actual page files
43
+ File.open(view.dir/:pages/"about.html", "w") do |f|
44
+ f.puts "<html><head><title>About Us</title></head><body>"
45
+ f.puts "<h1>About Us</h1>"
46
+ f.puts "<p>Learn more about our company and mission</p>"
47
+ f.puts "<p><a href=\"index.html\">← Back to Home</a></p>"
48
+ f.puts "</body></html>"
49
+ end
50
+ File.open(view.dir/:pages/"contact.html", "w") do |f|
51
+ f.puts "<html><head><title>Contact</title></head><body>"
52
+ f.puts "<h1>Contact</h1>"
53
+ f.puts "<p>Get in touch with our team</p>"
54
+ f.puts "<p><a href=\"index.html\">← Back to Home</a></a></p>"
55
+ f.puts "</body></html>"
56
+ end
57
+
58
+ see_file pages_dir/"list.txt"
59
+ see_file view.dir/:pages/"about.html"
60
+ see_file view.dir/:pages/"contact.html"
61
+
62
+ view.generate_front_page
63
+
64
+ instruct <<~EOS
65
+ Again, 13 posts in three views
66
+ blog3 will have only header, main, right
67
+ It will have both the Links widget and Pages widget in the right sidebar
68
+ Click the + to expand widgets
69
+ Links open in new tabs
70
+ Pages show dummy content (About Us and Contact)
71
+ EOS
72
+
73
+ examine view
@@ -0,0 +1,120 @@
1
+ # Manual inspection tests for BannerSVG feature combinations
2
+ #
3
+ # This file creates posts with sensible combinations of banner features,
4
+ # demonstrating how multiple features work together in realistic scenarios.
5
+ # The posts are displayed in a browser for visual inspection.
6
+ #
7
+ # Usage:
8
+ # ruby test_banner_combinations.rb # Interactive mode with browser
9
+ # ruby test_banner_combinations.rb --automated # Automated mode for CI/AI testing
10
+ #
11
+ # Combinations tested:
12
+ # - Professional Blue: Dark blue background with white bold centered text
13
+ # - Gradient Elegance: Purple gradient with white bold italic centered text
14
+ # - Warm Welcome: Yellow radial gradient with dark text, left-aligned
15
+ # - High Contrast: Black background with large white bold text
16
+ # - Reverse Contrast: White background with black text, right-aligned
17
+ # - Creative Gradient: Diagonal gradient with white bold centered text
18
+ # - Subtle Elegance: Light gray with dark italic text
19
+ # - Bold Statement: Red gradient with large white bold text
20
+ # - Modern Minimal: Light background with smaller dark text
21
+ #
22
+ require_relative "./banner_environment"
23
+
24
+ # Create test banners for sensible combinations
25
+ create_banner_post(
26
+ "Professional Blue",
27
+ "Clean and modern design",
28
+ "back.color #1e3a8a\ntext.color #ffffff\ntitle.style bold\ntext.position center\ntitle.xy 50% 30%\nsubtitle.xy 50% 70%",
29
+ "Professional Blue (White text, bold, centered)",
30
+ "banner-combo-test"
31
+ )
32
+
33
+ create_banner_post(
34
+ "Gradient Elegance",
35
+ "Smooth color transition",
36
+ "back.linear #667eea #764ba2 lr\ntext.color #ffffff\ntitle.style bold italic\ntext.position center\ntitle.xy 50% 30%\nsubtitle.xy 50% 70%",
37
+ "Gradient Elegance (Purple gradient, white text, bold italic, centered)",
38
+ "banner-combo-test"
39
+ )
40
+
41
+ create_banner_post(
42
+ "Warm Welcome",
43
+ "Friendly and inviting",
44
+ "back.radial #fbbf24 #f59e0b\ntext.color #1f2937\ntitle.style bold\ntext.position left\ntitle.xy 10% 30%\nsubtitle.xy 10% 70%",
45
+ "Warm Welcome (Yellow radial, dark text, bold, left-aligned)",
46
+ "banner-combo-test"
47
+ )
48
+
49
+ create_banner_post(
50
+ "High Contrast",
51
+ "Maximum readability",
52
+ "back.color #000000\ntext.color #ffffff\ntitle.style bold\ntitle.scale 1.2\nsubtitle.scale 0.8\ntext.position center\ntitle.xy 50% 30%\nsubtitle.xy 50% 70%",
53
+ "High Contrast (Black background, white text, bold, large, centered)",
54
+ "banner-combo-test"
55
+ )
56
+
57
+ create_banner_post(
58
+ "Reverse Contrast",
59
+ "Inverted color scheme",
60
+ "back.color #ffffff\ntext.color #000000\ntitle.style bold\ntext.position right\ntitle.xy 90% 30%\nsubtitle.xy 90% 70%",
61
+ "Reverse Contrast (White background, black text, bold, right-aligned)",
62
+ "banner-combo-test"
63
+ )
64
+
65
+ create_banner_post(
66
+ "Creative Gradient",
67
+ "Diagonal color flow",
68
+ "back.linear #ff6b6b #4ecdc4 ul-lr\ntext.color #ffffff\ntitle.style bold\ntitle.scale 1.1\nsubtitle.scale 0.7\ntext.position center\ntitle.xy 50% 30%\nsubtitle.xy 50% 70%",
69
+ "Creative Gradient (Diagonal gradient, white text, bold, centered)",
70
+ "banner-combo-test"
71
+ )
72
+
73
+ create_banner_post(
74
+ "Subtle Elegance",
75
+ "Understated beauty",
76
+ "back.color #f8fafc\ntext.color #475569\ntitle.style italic\ntext.position center\ntitle.xy 50% 35%\nsubtitle.xy 50% 75%",
77
+ "Subtle Elegance (Light gray background, dark text, italic, centered)",
78
+ "banner-combo-test"
79
+ )
80
+
81
+ create_banner_post(
82
+ "Bold Statement",
83
+ "Make an impact",
84
+ "back.linear #dc2626 #7c2d12 tb\ntext.color #ffffff\ntitle.style bold\ntitle.scale 1.3\nsubtitle.scale 0.9\ntext.position center\ntitle.xy 50% 25%\nsubtitle.xy 50% 75%",
85
+ "Bold Statement (Red gradient, white text, bold, large, centered)",
86
+ "banner-combo-test"
87
+ )
88
+
89
+ create_banner_post(
90
+ "Modern Minimal",
91
+ "Clean and simple",
92
+ "back.color #f1f5f9\ntext.color #0f172a\ntitle.style bold\ntitle.scale 0.9\nsubtitle.scale 0.6\ntext.position left\ntitle.xy 5% 40%\nsubtitle.xy 5% 80%",
93
+ "Modern Minimal (Light background, dark text, bold, left-aligned, smaller)",
94
+ "banner-combo-test"
95
+ )
96
+
97
+ generate_front_page("banner-combo-test")
98
+
99
+ instruct <<~EOS
100
+ BannerSVG Combination Tests
101
+
102
+ This page displays posts with sensible feature combinations:
103
+ - Professional Blue: Dark blue background with white bold centered text
104
+ - Gradient Elegance: Purple gradient with white bold italic centered text
105
+ - Warm Welcome: Yellow radial gradient with dark text, left-aligned
106
+ - High Contrast: Black background with large white bold text
107
+ - Reverse Contrast: White background with black text, right-aligned
108
+ - Creative Gradient: Diagonal gradient with white bold centered text
109
+ - Subtle Elegance: Light gray with dark italic text
110
+ - Bold Statement: Red gradient with large white bold text
111
+ - Modern Minimal: Light background with smaller dark text
112
+
113
+ Each combination shows how multiple features work together.
114
+ Check that gradients render properly, text positioning is correct,
115
+ and the overall design looks cohesive and professional.
116
+
117
+ The JavaScript should handle dynamic resizing when you resize the browser window.
118
+ EOS
119
+
120
+ examine("banner-combo-test")