streamtex 0.7.44__tar.gz → 0.7.45__tar.gz

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 (164) hide show
  1. {streamtex-0.7.44/streamtex.egg-info → streamtex-0.7.45}/PKG-INFO +1 -1
  2. {streamtex-0.7.44 → streamtex-0.7.45}/pyproject.toml +1 -1
  3. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/history.py +7 -0
  4. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/book.py +65 -9
  5. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/deploy_cmd.py +10 -0
  6. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export.py +31 -2
  7. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image.py +0 -1
  8. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_editor.py +9 -11
  9. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/media_overlay.py +18 -1
  10. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/pdf_export.py +32 -2
  11. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/default.css +3 -0
  12. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/__init__.py +1 -1
  13. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/base.py +16 -14
  14. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/container.py +63 -0
  15. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/text.py +12 -12
  16. {streamtex-0.7.44 → streamtex-0.7.45/streamtex.egg-info}/PKG-INFO +1 -1
  17. {streamtex-0.7.44 → streamtex-0.7.45}/LICENSE +0 -0
  18. {streamtex-0.7.44 → streamtex-0.7.45}/MANIFEST.in +0 -0
  19. {streamtex-0.7.44 → streamtex-0.7.45}/README.md +0 -0
  20. {streamtex-0.7.44 → streamtex-0.7.45}/setup.cfg +0 -0
  21. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/__init__.py +0 -0
  22. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/__main__.py +0 -0
  23. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/__init__.py +0 -0
  24. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/config.py +0 -0
  25. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/generate.py +0 -0
  26. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/metadata.py +0 -0
  27. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/__init__.py +0 -0
  28. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/base.py +0 -0
  29. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/fal.py +0 -0
  30. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/google.py +0 -0
  31. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/openai.py +0 -0
  32. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/registry.py +0 -0
  33. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/auth.py +0 -0
  34. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/banner.py +0 -0
  35. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/bib.py +0 -0
  36. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/bib_preview.py +0 -0
  37. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/block_helpers.py +0 -0
  38. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/blocks.py +0 -0
  39. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/browser.py +0 -0
  40. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/__init__.py +0 -0
  41. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_claude_files.py +0 -0
  42. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_divergence.py +0 -0
  43. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_install_local_packs.py +0 -0
  44. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_shared.py +0 -0
  45. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_stx_toml.py +0 -0
  46. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_toml_helpers.py +0 -0
  47. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/artifact_cmd.py +0 -0
  48. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/bib_cmd.py +0 -0
  49. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/build_check.py +0 -0
  50. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/cache_cmd.py +0 -0
  51. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_cmd.py +0 -0
  52. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_global.py +0 -0
  53. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_project.py +0 -0
  54. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/commands.py +0 -0
  55. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/component_cmd.py +0 -0
  56. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/console.py +0 -0
  57. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/coolify.py +0 -0
  58. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/deploy_auth_cmd.py +0 -0
  59. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/dev_cmd.py +0 -0
  60. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/dev_config.py +0 -0
  61. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/ds_cmd.py +0 -0
  62. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/export_cmd.py +0 -0
  63. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/install_cmd.py +0 -0
  64. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/kit_cmd.py +0 -0
  65. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/main.py +0 -0
  66. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/__init__.py +0 -0
  67. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/base.py +0 -0
  68. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/compat.py +0 -0
  69. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/registry.py +0 -0
  70. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/v0_4_0.py +0 -0
  71. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/v0_4_6.py +0 -0
  72. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/pack_cmd.py +0 -0
  73. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/project_cmd.py +0 -0
  74. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/project_rules.py +0 -0
  75. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/publish_cmd.py +0 -0
  76. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/published_check.py +0 -0
  77. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/run_cmd.py +0 -0
  78. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/run_set.py +0 -0
  79. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/screenshot_cmd.py +0 -0
  80. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/shortcuts.py +0 -0
  81. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/status_cmd.py +0 -0
  82. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/sync_cmd.py +0 -0
  83. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/upgrade_cmd.py +0 -0
  84. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/validate_cmd.py +0 -0
  85. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/workspace_cmd.py +0 -0
  86. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/code.py +0 -0
  87. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/collection.py +0 -0
  88. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/constants.py +0 -0
  89. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/container.py +0 -0
  90. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/__init__.py +0 -0
  91. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/__init__.py +0 -0
  92. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/_frontmatter.py +0 -0
  93. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/agent.py +0 -0
  94. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/ai_prompt.py +0 -0
  95. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/archetype.py +0 -0
  96. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/asset.py +0 -0
  97. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/discovery.py +0 -0
  98. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/guideline.py +0 -0
  99. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/install.py +0 -0
  100. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/integration.py +0 -0
  101. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/loaders.py +0 -0
  102. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/palette.py +0 -0
  103. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/skill.py +0 -0
  104. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/spec.py +0 -0
  105. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/validation.py +0 -0
  106. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/contracts.py +0 -0
  107. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/discovery.py +0 -0
  108. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/validation.py +0 -0
  109. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deeplink.py +0 -0
  110. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deploy/__init__.py +0 -0
  111. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deploy/stx_auth.py +0 -0
  112. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/enums.py +0 -0
  113. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export_enrich.py +0 -0
  114. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export_widgets.py +0 -0
  115. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/facts.py +0 -0
  116. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/grid.py +0 -0
  117. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/gsheet.py +0 -0
  118. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/hover_tooltip.py +0 -0
  119. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/i18n.py +0 -0
  120. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_crop.py +0 -0
  121. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_utils.py +0 -0
  122. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/inspector.py +0 -0
  123. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/kept.py +0 -0
  124. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/latex.py +0 -0
  125. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/latex_utils.py +0 -0
  126. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/link_config.py +0 -0
  127. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/link_preview.py +0 -0
  128. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/list.py +0 -0
  129. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/loading.py +0 -0
  130. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/markdown.py +0 -0
  131. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/marker.py +0 -0
  132. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/marker_runtime.py +0 -0
  133. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/mermaid.py +0 -0
  134. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/overlay.py +0 -0
  135. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/plantuml.py +0 -0
  136. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/presentation.py +0 -0
  137. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/presentation_profile.py +0 -0
  138. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/runtime_flags.py +0 -0
  139. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/scripts/__init__.py +0 -0
  140. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/scripts/migrate_curves_to_relative.py +0 -0
  141. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/search.py +0 -0
  142. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/slide.py +0 -0
  143. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/space.py +0 -0
  144. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/spacing.py +0 -0
  145. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/css/stx_global.css +0 -0
  146. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/js/stx_marker_observer.js +0 -0
  147. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/js/stx_scroll_spy.js +0 -0
  148. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/logo-stx-tiny.png +0 -0
  149. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/logo-stx.png +0 -0
  150. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/core.py +0 -0
  151. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/scale.py +0 -0
  152. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/scale_curves.toml +0 -0
  153. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/visibility.py +0 -0
  154. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/tikz.py +0 -0
  155. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/toc.py +0 -0
  156. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/utils.py +0 -0
  157. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/watch.py +0 -0
  158. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/write.py +0 -0
  159. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/zoom.py +0 -0
  160. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/SOURCES.txt +0 -0
  161. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/dependency_links.txt +0 -0
  162. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/entry_points.txt +0 -0
  163. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/requires.txt +0 -0
  164. {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamtex
3
- Version: 0.7.44
3
+ Version: 0.7.45
4
4
  Summary: AI-powered content framework for Streamlit — create presentations, courses, and web-books with Claude or Cursor, no coding required.
5
5
  Author-email: Nicolas Guelfi <nicolas.guelfi@laposte.net>
6
6
  License-Expression: BUSL-1.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "streamtex"
7
- version = "0.7.44"
7
+ version = "0.7.45"
8
8
  description = "AI-powered content framework for Streamlit — create presentations, courses, and web-books with Claude or Cursor, no coding required."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -111,6 +111,13 @@ def save_version(
111
111
  base_image=base_image,
112
112
  revised_prompt=revised_prompt,
113
113
  )
114
+ if existing_meta:
115
+ # The display settings belong to the slot, not to one image: a new
116
+ # version keeps the author's zoom and size (#18).
117
+ meta.display_zoom = existing_meta.display_zoom
118
+ meta.display_width = existing_meta.display_width
119
+ meta.display_height = existing_meta.display_height
120
+ meta.display_keep_ratio = existing_meta.display_keep_ratio
114
121
  save_metadata(meta, current_json)
115
122
 
116
123
  return current
@@ -21,6 +21,7 @@ from .enums import Tags
21
21
  from .export import (
22
22
  ExportConfig,
23
23
  ExportMode,
24
+ apply_view_to_export_html,
24
25
  build_export_filename,
25
26
  generate_export_html,
26
27
  get_asset_collector,
@@ -215,7 +216,8 @@ def _resolve_pdf_base_url() -> str:
215
216
 
216
217
  def _offer_export_downloads(html: str, base_name: str,
217
218
  pdf_config: PdfConfig | None = None,
218
- paginated: bool = False) -> None:
219
+ paginated: bool = False,
220
+ html_zoom: int = 0) -> None:
219
221
  """Render a 'Download as...' expander with export options and download buttons.
220
222
 
221
223
  Format checkboxes (HTML / PDF) live **outside** the form so they trigger
@@ -232,7 +234,7 @@ def _offer_export_downloads(html: str, base_name: str,
232
234
  # widget keys change, forcing Streamlit to create fresh widgets with
233
235
  # the new default values. Old orphaned keys are harmless.
234
236
  _cfg_str = (f"{defaults.format}|{defaults.landscape}|{defaults.print_background}|"
235
- f"{defaults.page_numbers}|{defaults.scale}|{defaults.content_width}|"
237
+ f"{defaults.page_numbers}|{defaults.scale}|{defaults.content_width}|{defaults.zoom}|"
236
238
  f"{defaults.margin_top}|{defaults.margin_bottom}|"
237
239
  f"{defaults.margin_left}|{defaults.margin_right}")
238
240
  _cfg_fp = hashlib.md5(_cfg_str.encode()).hexdigest()[:6]
@@ -287,7 +289,16 @@ def _offer_export_downloads(html: str, base_name: str,
287
289
  # ── HTML enrichment options ─────────────────────────
288
290
  enrich_nav = False
289
291
  enrich_title = ""
292
+ html_zoom_pct = 0
290
293
  if want_html:
294
+ html_zoom_pct = st.number_input(
295
+ "Zoom HTML (%)", min_value=0, max_value=400,
296
+ value=max(0, min(400, int(html_zoom or 0))), step=10,
297
+ key=f"_stx_export_html_zoom_{_pk}_{html_zoom}",
298
+ help="0 = the HTML file follows the view zoom (Settings › Zoom %). "
299
+ "A value fixes the zoom of the HTML file whatever the screen "
300
+ "shows; it replaces the view zoom (the PDF zoom multiplies it).",
301
+ )
291
302
  st.markdown("**HTML navigation**")
292
303
  enrich_nav = st.checkbox(
293
304
  "Enriched navigation (TOC sidebar, markers, search)",
@@ -316,6 +327,7 @@ def _offer_export_downloads(html: str, base_name: str,
316
327
  pdf_background = defaults.print_background
317
328
  pdf_page_numbers = defaults.page_numbers
318
329
  pdf_scale = defaults.scale
330
+ pdf_zoom = defaults.zoom
319
331
  pdf_content_width = defaults.content_width
320
332
  m_top = _margin_mm(defaults.margin_top)
321
333
  m_bottom = _margin_mm(defaults.margin_bottom)
@@ -326,7 +338,8 @@ def _offer_export_downloads(html: str, base_name: str,
326
338
  _cur_w = st.session_state.get(_PAGE_WIDTH_KEY, 100)
327
339
  _cur_z = st.session_state.get(_ZOOM_KEY, 100)
328
340
  st.markdown("**PDF layout**")
329
- st.caption(f"Current view: Width {_cur_w}% · Zoom {_cur_z}%")
341
+ st.caption(f"Current view: Zoom {_cur_z}% (carried into the PDF, times Zoom below) · "
342
+ f"Width {_cur_w}% (HTML only; the PDF uses Content width)")
330
343
  _FORMAT_OPTIONS = [
331
344
  "16:9", "16:10", "4:3",
332
345
  "A4", "A3", "Letter", "Legal", "Tabloid",
@@ -370,8 +383,19 @@ def _offer_export_downloads(html: str, base_name: str,
370
383
  "Scale (%)", min_value=10, max_value=500,
371
384
  value=_scale_default, step=10,
372
385
  key=f"_stx_pdf_scale_{_pk}",
386
+ help="Chrome's print scale: shrinks sizes in pt/px and lets the "
387
+ "text reflow; sizes in vw/vh stay proportional to the page. "
388
+ "To shrink everything, use Zoom.",
373
389
  )
374
390
  pdf_scale = _pdf_scale_pct / 100.0
391
+ pdf_zoom = st.number_input(
392
+ "Zoom (%)", min_value=10, max_value=400,
393
+ value=max(10, min(400, int(defaults.zoom))), step=10,
394
+ key=f"_stx_pdf_zoom_{_pk}",
395
+ help="Shrinks or enlarges everything in the PDF, sizes in vw/vh "
396
+ "included; multiplies with the view zoom (view 80 % x 70 % = 56 %). "
397
+ "Scale only shrinks pt/px sizes.",
398
+ )
375
399
  _cw_default = max(10, min(100, defaults.content_width))
376
400
  pdf_content_width = st.number_input(
377
401
  "Content width (%)", min_value=10, max_value=100,
@@ -404,11 +428,26 @@ def _offer_export_downloads(html: str, base_name: str,
404
428
  rebuild_fn()
405
429
  st.rerun()
406
430
  if full_book_html:
407
- effective_html = full_book_html
431
+ # Built once per session with the view of that moment:
432
+ # carry the current view zoom and width (#110).
433
+ effective_html = apply_view_to_export_html(
434
+ full_book_html,
435
+ built_view=st.session_state.get(_STX_FULL_EXPORT_VIEW_KEY),
436
+ page_width=f"{st.session_state.get(_PAGE_WIDTH_KEY, 100)}%",
437
+ zoom=st.session_state.get(_ZOOM_KEY, 100) / 100,
438
+ )
408
439
 
409
440
  if want_html:
410
441
  # Enrich a separate copy — keep effective_html raw for PDF
411
442
  html_download = effective_html
443
+ if html_zoom_pct:
444
+ # A fixed zoom for the HTML file only (#110); the PDF
445
+ # keeps the view zoom times its own Zoom.
446
+ html_download = apply_view_to_export_html(
447
+ html_download,
448
+ page_width=f"{st.session_state.get(_PAGE_WIDTH_KEY, 100)}%",
449
+ zoom=html_zoom_pct / 100,
450
+ )
412
451
  if enrich_nav:
413
452
  try:
414
453
  from .export_enrich import enrich_export_html
@@ -464,6 +503,7 @@ def _offer_export_downloads(html: str, base_name: str,
464
503
  margin_right=f"{m_right}mm",
465
504
  print_background=pdf_background,
466
505
  scale=pdf_scale,
506
+ zoom=pdf_zoom,
467
507
  content_width=pdf_content_width,
468
508
  page_numbers=pdf_page_numbers,
469
509
  header_template=defaults.header_template,
@@ -482,6 +522,9 @@ def _offer_export_downloads(html: str, base_name: str,
482
522
  report=_report)
483
523
  if _report.timed_out: # shown with the download button
484
524
  results["pdf_warning"] = _report.message(cfg.load_timeout_ms)
525
+ _view_z = st.session_state.get(_ZOOM_KEY, 100)
526
+ results["pdf_zoom_note"] = (
527
+ f"PDF zoom: view {_view_z:g}% × {pdf_zoom:g}% = {_view_z * pdf_zoom / 100:g}%")
485
528
  except Exception as exc:
486
529
  st.error(f"PDF generation failed after {time.monotonic() - _t0:.0f} s: {exc}")
487
530
  st.session_state[state_key] = results
@@ -505,6 +548,8 @@ def _offer_export_downloads(html: str, base_name: str,
505
548
  key=f"_stx_dl_html_{base_name}",
506
549
  )
507
550
  if "pdf" in results:
551
+ if results.get("pdf_zoom_note"):
552
+ st.caption(results["pdf_zoom_note"])
508
553
  if results.get("pdf_warning"):
509
554
  st.warning(results["pdf_warning"])
510
555
  st.download_button(
@@ -639,7 +684,8 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
639
684
  scale=None,
640
685
  block_args: tuple = (),
641
686
  block_kwargs: dict | None = None,
642
- lang: str | None = None):
687
+ lang: str | None = None,
688
+ html_export_zoom: int = 0):
643
689
  """Generates a web page e-book from a list of block modules.
644
690
 
645
691
  :param separator: Optional module with a build() function, rendered between each block.
@@ -663,6 +709,10 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
663
709
  :param page_width: Page width as % of browser width (default 90).
664
710
  :param zoom: Default zoom level as % (default 100).
665
711
  :param loading: If True (default), show a full-screen overlay with progress during loading.
712
+ :param html_export_zoom: Starting value of the "Zoom HTML (%)" field of the
713
+ export panel (#110). ``0`` (default) = the exported HTML follows the
714
+ view zoom (Settings › Zoom %); a value fixes the zoom of the HTML
715
+ file whatever the screen shows.
666
716
  :param scale: Optional ``ScaleConfig`` to override the indexed responsive
667
717
  font scale for this document only. When provided, an inline ``<style>``
668
718
  block is emitted after the default CSS load, redefining
@@ -908,6 +958,7 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
908
958
  pdf_config=pdf_config, exports=exports,
909
959
  inspector=inspector,
910
960
  page_width=page_width, zoom=zoom,
961
+ html_export_zoom=html_export_zoom,
911
962
  profile_names=profile_names,
912
963
  active_profile=active_name,
913
964
  profile_modified=_profile_modified,
@@ -1098,7 +1149,8 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
1098
1149
  )
1099
1150
  if _show_manual:
1100
1151
  with st.sidebar:
1101
- _offer_export_downloads(full_html, base_name, pdf_config=pdf_config)
1152
+ _offer_export_downloads(full_html, base_name, pdf_config=pdf_config,
1153
+ html_zoom=html_export_zoom)
1102
1154
 
1103
1155
  # --- Inspector panel (opt-in, rendered into reserved sidebar placeholder) ---
1104
1156
  if _inspector_placeholder is not None and st.session_state.get("_stx_inspector_open", False):
@@ -1612,6 +1664,7 @@ def _isolate_widget_keys():
1612
1664
 
1613
1665
 
1614
1666
  _STX_FULL_EXPORT_HTML_KEY = "_stx_full_export_html"
1667
+ _STX_FULL_EXPORT_VIEW_KEY = "_stx_full_export_view" # (page_width, zoom) it was built with
1615
1668
 
1616
1669
 
1617
1670
  # Streamlit primitives whose DOM emission is suppressed during the
@@ -1789,6 +1842,7 @@ def _build_page_cache(module_list, toc_config, marker_config, separator,
1789
1842
  full_html = generate_export_html()
1790
1843
  if full_html:
1791
1844
  st.session_state[_STX_FULL_EXPORT_HTML_KEY] = full_html
1845
+ st.session_state[_STX_FULL_EXPORT_VIEW_KEY] = (export_config.page_width, export_config.zoom)
1792
1846
  reset_export_buffer(ExportConfig(enabled=False))
1793
1847
 
1794
1848
  search_index = stop_collector() if use_search else None
@@ -2422,7 +2476,8 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
2422
2476
  profile_modified=False, all_profiles=None,
2423
2477
  loading=False,
2424
2478
  block_args: tuple = (),
2425
- block_kwargs: dict | None = None):
2479
+ block_kwargs: dict | None = None,
2480
+ html_export_zoom: int = 0):
2426
2481
  """Paginated rendering — only renders one block per rerun."""
2427
2482
  start_time = time.time()
2428
2483
  logger.debug("Starting st_book (paginated)...")
@@ -2725,7 +2780,8 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
2725
2780
  if _show_manual:
2726
2781
  with st.sidebar:
2727
2782
  _offer_export_downloads(section_html, base_name,
2728
- pdf_config=pdf_config, paginated=True)
2783
+ pdf_config=pdf_config, paginated=True,
2784
+ html_zoom=html_export_zoom)
2729
2785
 
2730
2786
  # --- Marker navigation widget (ALL markers, cross-page aware) ---
2731
2787
  # Skip the widget if the current page has no markers (e.g. footer)
@@ -2803,7 +2859,7 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
2803
2859
  #: configuration only — nothing that reaches the content of a block.
2804
2860
  BOOK_DEFAULT_KEYS = frozenset({
2805
2861
  "paginate", "page_width", "zoom", "export", "export_title", "loading",
2806
- "chrome_banner", "banner_color", "doc_version", "lang",
2862
+ "chrome_banner", "banner_color", "doc_version", "lang", "html_export_zoom",
2807
2863
  })
2808
2864
 
2809
2865
 
@@ -113,6 +113,16 @@ sys.exit(1) if i < r else sys.exit(0)" || \\
113
113
  # Copy project files
114
114
  COPY . .
115
115
 
116
+ # LaTeX for st_tikz (latex -> dvisvgm), only when the project's code uses it
117
+ # (#17): about 540 MB. The image's own .venv also mentions st_tikz, hence
118
+ # --exclude-dir.
119
+ RUN if grep -rqs --include='*.py' --exclude-dir=.venv "st_tikz" . ; then \\
120
+ apt-get update && apt-get install -y --no-install-recommends \\
121
+ texlive-latex-base texlive-latex-recommended texlive-latex-extra \\
122
+ texlive-pictures dvisvgm ghostscript \\
123
+ && rm -rf /var/lib/apt/lists/* ; \\
124
+ fi
125
+
116
126
  # Install any [[packs]] type="local" declared in stx.toml with a relative path.
117
127
  # Absolute paths are skipped — handle them upstream via `stx deploy preflight`
118
128
  # or by promoting the pack to git (`stx component promote`).
@@ -465,8 +465,12 @@ class HtmlExportBuffer:
465
465
  body = self.get_body_html()
466
466
  css = _load_default_css()
467
467
  c = self.config
468
- bg = c.theme_bg or _get_theme_color("theme.backgroundColor", "#fff")
469
- text = c.theme_text or _get_theme_color("theme.textColor", "#333")
468
+ # ExportConfig > the session's theme (kept by st_book, as the PDF
469
+ # uses it; a runtime light/dark toggle will write there, #20) > config.
470
+ bg = (c.theme_bg or _session_theme_color("_stx_theme_bg")
471
+ or _get_theme_color("theme.backgroundColor", "#fff"))
472
+ text = (c.theme_text or _session_theme_color("_stx_theme_text")
473
+ or _get_theme_color("theme.textColor", "#333"))
470
474
  link = c.theme_primary or _get_theme_color("theme.primaryColor", "#1155cc")
471
475
  zoom_css = f" zoom: {c.zoom};\n" if c.zoom != 1.0 else ""
472
476
  return (
@@ -552,6 +556,15 @@ def _escape_lang(lang: str | None) -> str:
552
556
  return "en"
553
557
 
554
558
 
559
+ def _session_theme_color(key: str) -> str | None:
560
+ """A theme colour ``st_book`` keeps in session state, or None (no session)."""
561
+ try:
562
+ value = st.session_state.get(key)
563
+ except Exception: # noqa: BLE001 — outside a Streamlit session
564
+ return None
565
+ return value if isinstance(value, str) and value else None
566
+
567
+
555
568
  def _get_theme_color(option: str, fallback: str) -> str:
556
569
  """Read a Streamlit theme color from config, with fallback.
557
570
 
@@ -678,6 +691,22 @@ def export_pop_wrapper(close_tag: str) -> None:
678
691
  _buffer.pop_wrapper(close_tag)
679
692
 
680
693
 
694
+ def apply_view_to_export_html(html: str, *, built_view: Optional[tuple] = None,
695
+ page_width: str, zoom: float) -> str:
696
+ """Give an already generated export the current view's page width and zoom.
697
+
698
+ The full-book HTML is built once per session; when the view changed
699
+ since (*built_view* = ``(page_width, zoom)`` it was built with), a later
700
+ rule on ``.streamtex-page`` replaces the old values (#110). Blocks never
701
+ read the view's width or zoom, only this page rule does, so no rebuild
702
+ is needed. Unchanged when the view is the same.
703
+ """
704
+ if built_view is not None and tuple(built_view) == (page_width, zoom):
705
+ return html
706
+ css = f".streamtex-page {{ max-width: {page_width}; zoom: {zoom:g}; }}"
707
+ return html.replace("</head>", f"<style>{css}</style>\n</head>", 1)
708
+
709
+
681
710
  def generate_export_html() -> Optional[str]:
682
711
  """Return the full HTML document, or None when export is inactive."""
683
712
  if _buffer is None:
@@ -168,7 +168,6 @@ def st_image(
168
168
  _zoom = _st.session_state.get(f"{_prefix}_zoom") if _st else None
169
169
  _dw = _st.session_state.get(f"{_prefix}_width") if _st else None
170
170
  _dh = _st.session_state.get(f"{_prefix}_height") if _st else None
171
- # logger.warning("[DIAG:APPLY] '%s' already_init=%s, zoom=%s, dw=%s, dh=%s, original_width=%s", name, _already_init, _zoom, _dw, _dh, width)
172
171
  if _dw:
173
172
  width = _dw
174
173
  elif _zoom is not None and _zoom != 100:
@@ -473,17 +473,17 @@ def _load_display_from_metadata(name: str, prefix: str) -> None:
473
473
  """Load persisted display settings from metadata JSON into session_state.
474
474
 
475
475
  Called once per session — if session_state already has values, skip.
476
+ A load that fails is retried on the next run (#18): the flag is set
477
+ only once the metadata was read, or found absent.
476
478
  """
477
479
  init_key = f"{prefix}_initialized"
478
480
  if st.session_state.get(init_key):
479
481
  return
480
- st.session_state[init_key] = True
481
482
 
482
483
  try:
483
484
  from .ai.history import get_current_metadata
484
485
  meta = get_current_metadata(name)
485
486
  if meta:
486
- # logger.warning("[DIAG:LOAD] '%s' zoom=%s width=%s", name, meta.display_zoom, meta.display_width)
487
487
  if meta.display_zoom is not None:
488
488
  st.session_state.setdefault(f"{prefix}_zoom", meta.display_zoom)
489
489
  if meta.display_width is not None:
@@ -492,12 +492,11 @@ def _load_display_from_metadata(name: str, prefix: str) -> None:
492
492
  st.session_state.setdefault(f"{prefix}_height", meta.display_height)
493
493
  if meta.display_keep_ratio is not None:
494
494
  st.session_state.setdefault(f"{prefix}_keep_ratio", meta.display_keep_ratio)
495
+ st.session_state[init_key] = True
495
496
  except Exception:
496
- # Best-effort load: if no metadata file exists yet, or it is unreadable,
497
- # the image renders with its default display settings (no zoom/width
498
- # restoration). Not surfaced to the user — saved settings are an
499
- # enhancement, not a correctness requirement.
500
- pass
497
+ # Best-effort load: the image renders with its default display
498
+ # settings this run, and the load is tried again on the next one.
499
+ logger.debug("Display settings of '%s' not loaded; will retry", name, exc_info=True)
501
500
 
502
501
 
503
502
  def _persist_display_to_metadata(name: str, prefix: str) -> None:
@@ -508,11 +507,11 @@ def _persist_display_to_metadata(name: str, prefix: str) -> None:
508
507
 
509
508
  current = get_current(name)
510
509
  if not current:
511
- logger.warning("[DIAG:PERSIST] '%s' get_current() returned None — cannot persist zoom", name)
510
+ logger.warning("Display settings of '%s' not saved: no current image", name)
512
511
  return
513
512
  meta = get_current_metadata(name)
514
513
  if not meta:
515
- logger.warning("[DIAG:PERSIST] '%s' get_current_metadata() returned None — cannot persist zoom", name)
514
+ logger.warning("Display settings of '%s' not saved: no metadata", name)
516
515
  return
517
516
 
518
517
  meta.display_zoom = st.session_state.get(f"{prefix}_zoom")
@@ -521,9 +520,8 @@ def _persist_display_to_metadata(name: str, prefix: str) -> None:
521
520
  meta.display_keep_ratio = st.session_state.get(f"{prefix}_keep_ratio", True)
522
521
  json_path = metadata_path_for(current)
523
522
  save_metadata(meta, json_path)
524
- logger.warning("[DIAG:PERSIST] '%s' saved display_zoom=%s to %s", name, meta.display_zoom, json_path)
525
523
  except Exception:
526
- logger.warning("[DIAG:PERSIST] '%s' EXCEPTION in _persist_display_to_metadata", name, exc_info=True)
524
+ logger.warning("Display settings of '%s' not saved", name, exc_info=True)
527
525
 
528
526
 
529
527
  def _render_display_tab(*, name, width, height, key_base):
@@ -34,6 +34,11 @@ _BADGE_BASE_CSS = (
34
34
  "pointer-events:none; z-index:2;"
35
35
  )
36
36
 
37
+ # Opt-in proportional pill (#55): sized on the IMAGE width (container query
38
+ # units resolve against the media box, declared a size container).
39
+ _BADGE_PROPORTIONAL_CSS = _BADGE_BASE_CSS.replace(
40
+ "padding:2px 8px; font-size:12px;", "padding:0.15em 0.6em; font-size:clamp(9px, 7cqw, 22px);")
41
+
37
42
  # margin declarations carrying an ``auto`` value (centering). They must be
38
43
  # transferred from the media style onto the wrapper — an auto margin left on
39
44
  # the media alone stops centering once the media is boxed in a wrapper.
@@ -51,12 +56,21 @@ class MediaOverlay:
51
56
  overrides it in cascade order) — e.g. a project convention.
52
57
  :param aria_label: Accessible disclosure text (e.g. AI-transparency
53
58
  wording, art. 50 AI Act); emitted as ``aria-label`` when non-empty.
59
+ :param proportional: Size the badge on the image rather than the
60
+ viewport (#55): the media box becomes a size container and the
61
+ default pill uses ``clamp(9px, 7cqw, 22px)``, so a thumbnail and a
62
+ hero get proportionate badges; ``cqw`` also works in ``css``. Needs
63
+ a definite width (``st_image``'s default ``100%`` or an explicit
64
+ one): with ``width="auto"`` the box is left as is (a size container
65
+ cannot shrink-wrap its image) and ``cqw`` falls back to the viewport.
66
+ Off by default: output byte-identical to earlier versions.
54
67
  """
55
68
 
56
69
  text: str
57
70
  position: str = "bottom-right"
58
71
  css: str = ""
59
72
  aria_label: str = ""
73
+ proportional: bool = False
60
74
 
61
75
  def __post_init__(self):
62
76
  if self.position not in _POSITION_CSS:
@@ -74,7 +88,8 @@ def build_overlay_span(overlay: MediaOverlay) -> str:
74
88
  guarantees the badge never intercepts clicks (link wrapping stays fully
75
89
  functional).
76
90
  """
77
- badge_css = f"position:absolute; {_POSITION_CSS[overlay.position]} {_BADGE_BASE_CSS}"
91
+ base = _BADGE_PROPORTIONAL_CSS if overlay.proportional else _BADGE_BASE_CSS
92
+ badge_css = f"position:absolute; {_POSITION_CSS[overlay.position]} {base}"
78
93
  if overlay.css:
79
94
  badge_css = f"{badge_css} {overlay.css}"
80
95
  aria_attr = (
@@ -122,6 +137,8 @@ def wrap_media_overlay(
122
137
  f"position:relative; display:inline-block; width:{box_width}; "
123
138
  f"max-width:100%; line-height:0;"
124
139
  )
140
+ if overlay.proportional and box_width not in ("auto", "fit-content"):
141
+ box_css = f"{box_css} container-type:inline-size;"
125
142
  return (
126
143
  f'<span class="stx-media-box" style="{box_css}">'
127
144
  f"{inner_html}{build_overlay_span(overlay)}</span>"
@@ -68,7 +68,20 @@ class PdfConfig:
68
68
  """Include background colors and gradients in the PDF."""
69
69
 
70
70
  scale: float = 1.0
71
- """Scale factor (0.1–2.0). 1.0 = 100%."""
71
+ """Chromium's print scale (0.1–2.0), like "Scale" in Chrome's print dialog.
72
+
73
+ Shrinks what is sized in ``pt``/``px`` and lets the text reflow on the
74
+ gained width; what is sized in ``vw``/``vh`` stays proportional to the
75
+ page (Chromium resolves viewport units against the printed page). To
76
+ shrink everything uniformly, use :attr:`zoom`."""
77
+
78
+ zoom: int = 100
79
+ """Zoom of the whole document in the PDF, in percent (10–400; #110).
80
+
81
+ A CSS ``zoom`` on ``body``, so it scales ``px``, ``pt``, ``vw`` and ``vh``
82
+ alike, and it multiplies with the view zoom the exported HTML already
83
+ carries (view 80 % × PDF 70 % = 56 %). The page still fills the paper
84
+ width; bookmarks and page breaks are unchanged. ``100`` = no change."""
72
85
 
73
86
  content_width: int = 100
74
87
  """Content width as percentage of the page (10–100).
@@ -333,18 +346,34 @@ def _inject_content_width_css(html: str, content_width: int) -> str:
333
346
  cw = max(10, min(100, content_width))
334
347
  padding_pct = (100 - cw) / 2
335
348
 
349
+ # Left and right only (#110): a percentage padding resolves against the
350
+ # width, so on all four sides it also pushed the content down by the
351
+ # same amount (15 % of the page width at 70 %).
336
352
  css = (
337
353
  f".streamtex-page {{"
338
354
  f" max-width: 100% !important;"
339
355
  f" width: 100% !important;"
340
356
  f" margin: 0 !important;"
341
- f" padding: {padding_pct:.1f}% !important;"
357
+ f" padding: 0 {padding_pct:.1f}% !important;"
342
358
  f" box-sizing: border-box !important;"
343
359
  f"}}"
344
360
  )
345
361
  return html.replace("</head>", f"<style>{css}</style>\n</head>")
346
362
 
347
363
 
364
+ def _inject_zoom_css(html: str, zoom: int) -> str:
365
+ """Zoom the whole document by *zoom* percent (CSS ``zoom`` on ``body``).
366
+
367
+ ``body`` rather than ``.streamtex-page``: the page already carries the
368
+ view zoom, and zooms on nested elements multiply, where a second rule on
369
+ the same element would replace it. No-op at 100.
370
+ """
371
+ z = max(10, min(400, int(zoom)))
372
+ if z == 100:
373
+ return html
374
+ return html.replace("</head>", f"<style>body {{ zoom: {z / 100:g}; }}</style>\n</head>")
375
+
376
+
348
377
  # ---------------------------------------------------------------------------
349
378
  # TOC → PDF Bookmarks
350
379
  # ---------------------------------------------------------------------------
@@ -532,6 +561,7 @@ def export_pdf(
532
561
 
533
562
  # Inject content-width CSS if narrower than 100%
534
563
  html = _inject_content_width_css(html, cfg.content_width)
564
+ html = _inject_zoom_css(html, cfg.zoom)
535
565
 
536
566
  # Inject TOC headings for PDF bookmark generation
537
567
  use_outline = False
@@ -68,6 +68,7 @@ a {
68
68
  --stx-GIANT-size: 196pt;
69
69
  --stx-Giant-size: 128pt;
70
70
  --stx-giant-size: 112pt;
71
+ --stx-HUGE-size: 104pt;
71
72
  --stx-Huge-size: 96pt;
72
73
  --stx-huge-size: 80pt;
73
74
  --stx-LARGE-size: 64pt;
@@ -130,6 +131,7 @@ a {
130
131
  --stx-GIANT-size: 128pt;
131
132
  --stx-Giant-size: 84pt;
132
133
  --stx-giant-size: 74pt;
134
+ --stx-HUGE-size: 69pt;
133
135
  --stx-Huge-size: 64pt;
134
136
  --stx-huge-size: 54pt;
135
137
  --stx-LARGE-size: 48pt;
@@ -192,6 +194,7 @@ a {
192
194
  --stx-GIANT-size: 80pt;
193
195
  --stx-Giant-size: 56pt;
194
196
  --stx-giant-size: 48pt;
197
+ --stx-HUGE-size: 45pt;
195
198
  --stx-Huge-size: 42pt;
196
199
  --stx-huge-size: 36pt;
197
200
  --stx-LARGE-size: 32pt;
@@ -18,7 +18,7 @@ from .text import (
18
18
  from .container import (
19
19
  BackgroundColors, Paddings, Margins, ContainerSizes,
20
20
  Flex, Layouts, Borders, Positions,
21
- ListStyles, GridStyles, Container,
21
+ ListStyles, GridStyles, CellAlignments, Container,
22
22
  )
23
23
 
24
24
  # Visibility
@@ -26,6 +26,8 @@ class StxStyles:
26
26
  reset_bold = text.weights.normal_weight
27
27
  italic = text.decors.italic_text
28
28
  center_txt = text.alignments.center_align
29
+ cell_center = container.cells.center
30
+ """Grid cell content centered both ways (``Container.cells.center_middle``)."""
29
31
 
30
32
  reset = (text.colors.reset + BackgroundColors.reset_bg
31
33
  + text.weights.normal_weight + text.sizes.medium_size + text.alignments.left_align)
@@ -35,33 +37,33 @@ class StxStyles:
35
37
  ``st_mermaid``, or ``st_tikz`` to make diagrams readable on dark pages."""
36
38
 
37
39
  GIANT = text.sizes.GIANT_size
38
- """196pt"""
40
+ """196pt on desktop (default.css)"""
39
41
  Giant = text.sizes.Giant_size
40
- """160pt"""
42
+ """128pt on desktop (default.css)"""
41
43
  giant = text.sizes.giant_size
42
- """128pt"""
44
+ """112pt on desktop (default.css)"""
43
45
  HUGE = text.sizes.HUGE_size
44
- """96pt"""
46
+ """104pt on desktop (default.css)"""
45
47
  Huge = text.sizes.Huge_size
46
- """80pt"""
48
+ """96pt on desktop (default.css)"""
47
49
  huge = text.sizes.huge_size
48
- """64pt"""
50
+ """80pt on desktop (default.css)"""
49
51
  LARGE = text.sizes.LARGE_size
50
- """48pt"""
52
+ """64pt on desktop (default.css)"""
51
53
  Large = text.sizes.Large_size
52
- """32pt"""
54
+ """48pt on desktop (default.css)"""
53
55
  large = text.sizes.large_size
54
- """24pt"""
56
+ """32pt on desktop (default.css)"""
55
57
  big = text.sizes.big_size
56
- """16pt"""
58
+ """24pt on desktop (default.css)"""
57
59
  medium = text.sizes.medium_size
58
- """12pt"""
60
+ """16pt on desktop (default.css)"""
59
61
  little = text.sizes.little_size
60
- """8pt"""
62
+ """12pt on desktop (default.css)"""
61
63
  small = text.sizes.small_size
62
- """6pt"""
64
+ """8pt on desktop (default.css)"""
63
65
  tiny = text.sizes.tiny_size
64
- """4pt"""
66
+ """4pt on desktop (default.css)"""
65
67
 
66
68
  # --- Indexed responsive scale (subscript access) ---
67
69
  class _IndexedScale: