figpack 0.4.2__tar.gz → 0.4.4__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 (90) hide show
  1. {figpack-0.4.2/figpack.egg-info → figpack-0.4.4}/PKG-INFO +3 -1
  2. {figpack-0.4.2 → figpack-0.4.4}/figpack/__init__.py +1 -1
  3. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_bundle_utils.py +3 -1
  4. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_figure_spec.py +90 -2
  5. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/figpack_view.py +41 -1
  6. figpack-0.4.4/figpack/figpack-figure-dist/assets/index-BdXsZMUB.css +1 -0
  7. figpack-0.4.4/figpack/figpack-figure-dist/assets/index-I4MhhmZQ.js +398 -0
  8. {figpack-0.4.2 → figpack-0.4.4}/figpack/figpack-figure-dist/index.html +2 -2
  9. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Box.py +7 -0
  10. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/CaptionedView.py +1 -0
  11. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Markdown.py +8 -0
  12. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/MultiChannelTimeseries.py +25 -0
  13. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Spectrogram.py +32 -0
  14. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Splitter.py +1 -0
  15. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/TabLayout.py +8 -0
  16. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/TimeseriesGraph.py +15 -1
  17. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/VerticalLayout.py +7 -0
  18. {figpack-0.4.2 → figpack-0.4.4/figpack.egg-info}/PKG-INFO +3 -1
  19. {figpack-0.4.2 → figpack-0.4.4}/figpack.egg-info/SOURCES.txt +2 -2
  20. {figpack-0.4.2 → figpack-0.4.4}/figpack.egg-info/requires.txt +2 -0
  21. {figpack-0.4.2 → figpack-0.4.4}/pyproject.toml +3 -1
  22. {figpack-0.4.2 → figpack-0.4.4}/tests/test_figure_spec.py +64 -4
  23. {figpack-0.4.2 → figpack-0.4.4}/tests/test_multichannel_timeseries.py +1 -0
  24. {figpack-0.4.2 → figpack-0.4.4}/tests/test_timeseries_graph.py +2 -0
  25. figpack-0.4.2/figpack/figpack-figure-dist/assets/index-DCvZuZLF.js +0 -398
  26. figpack-0.4.2/figpack/figpack-figure-dist/assets/index-oS-16pKZ.css +0 -1
  27. {figpack-0.4.2 → figpack-0.4.4}/LICENSE +0 -0
  28. {figpack-0.4.2 → figpack-0.4.4}/MANIFEST.in +0 -0
  29. {figpack-0.4.2 → figpack-0.4.4}/README.md +0 -0
  30. {figpack-0.4.2 → figpack-0.4.4}/figpack/cli.py +0 -0
  31. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/__init__.py +0 -0
  32. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_figure_utils.py +0 -0
  33. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_file_handler.py +0 -0
  34. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_patch_figure.py +0 -0
  35. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_remix_figure.py +0 -0
  36. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_remix_patch.py +0 -0
  37. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_revert_patch_figure.py +0 -0
  38. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_save_figure.py +0 -0
  39. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_server_manager.py +0 -0
  40. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_show_view.py +0 -0
  41. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_upload_bundle.py +0 -0
  42. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_view_figure.py +0 -0
  43. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/_zarr_consolidate.py +0 -0
  44. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/config.py +0 -0
  45. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/extension_view.py +0 -0
  46. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/figpack_extension.py +0 -0
  47. {figpack-0.4.2 → figpack-0.4.4}/figpack/core/zarr.py +0 -0
  48. {figpack-0.4.2 → figpack-0.4.4}/figpack/extensions.py +0 -0
  49. {figpack-0.4.2 → figpack-0.4.4}/figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  50. {figpack-0.4.2 → figpack-0.4.4}/figpack/utils/__init__.py +0 -0
  51. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/DataFrame.py +0 -0
  52. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Gallery.py +0 -0
  53. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/GalleryItem.py +0 -0
  54. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Iframe.py +0 -0
  55. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/Image.py +0 -0
  56. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/LayoutItem.py +0 -0
  57. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/MatplotlibFigure.py +0 -0
  58. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/MountainLayout.py +0 -0
  59. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/MountainLayoutItem.py +0 -0
  60. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/PlotlyExtension/PlotlyExtension.py +0 -0
  61. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/PlotlyExtension/__init__.py +0 -0
  62. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/PlotlyExtension/_plotly_extension.py +0 -0
  63. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/PlotlyExtension/plotly_view.js +0 -0
  64. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/TabLayoutItem.py +0 -0
  65. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/VerticalLayoutItem.py +0 -0
  66. {figpack-0.4.2 → figpack-0.4.4}/figpack/views/__init__.py +0 -0
  67. {figpack-0.4.2 → figpack-0.4.4}/figpack.egg-info/dependency_links.txt +0 -0
  68. {figpack-0.4.2 → figpack-0.4.4}/figpack.egg-info/entry_points.txt +0 -0
  69. {figpack-0.4.2 → figpack-0.4.4}/figpack.egg-info/top_level.txt +0 -0
  70. {figpack-0.4.2 → figpack-0.4.4}/setup.cfg +0 -0
  71. {figpack-0.4.2 → figpack-0.4.4}/tests/test_box.py +0 -0
  72. {figpack-0.4.2 → figpack-0.4.4}/tests/test_cli.py +0 -0
  73. {figpack-0.4.2 → figpack-0.4.4}/tests/test_core.py +0 -0
  74. {figpack-0.4.2 → figpack-0.4.4}/tests/test_dataframe.py +0 -0
  75. {figpack-0.4.2 → figpack-0.4.4}/tests/test_extension_system.py +0 -0
  76. {figpack-0.4.2 → figpack-0.4.4}/tests/test_figpack_view.py +0 -0
  77. {figpack-0.4.2 → figpack-0.4.4}/tests/test_file_handler.py +0 -0
  78. {figpack-0.4.2 → figpack-0.4.4}/tests/test_gallery.py +0 -0
  79. {figpack-0.4.2 → figpack-0.4.4}/tests/test_image.py +0 -0
  80. {figpack-0.4.2 → figpack-0.4.4}/tests/test_markdown.py +0 -0
  81. {figpack-0.4.2 → figpack-0.4.4}/tests/test_matplotlib_figure.py +0 -0
  82. {figpack-0.4.2 → figpack-0.4.4}/tests/test_patch_pages.py +0 -0
  83. {figpack-0.4.2 → figpack-0.4.4}/tests/test_plotly_figure.py +0 -0
  84. {figpack-0.4.2 → figpack-0.4.4}/tests/test_remix_patch.py +0 -0
  85. {figpack-0.4.2 → figpack-0.4.4}/tests/test_server_manager.py +0 -0
  86. {figpack-0.4.2 → figpack-0.4.4}/tests/test_spectrogram.py +0 -0
  87. {figpack-0.4.2 → figpack-0.4.4}/tests/test_splitter.py +0 -0
  88. {figpack-0.4.2 → figpack-0.4.4}/tests/test_tablayout.py +0 -0
  89. {figpack-0.4.2 → figpack-0.4.4}/tests/test_upload_bundle.py +0 -0
  90. {figpack-0.4.2 → figpack-0.4.4}/tests/test_view_figure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figpack
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: A Python package for creating shareable, interactive visualizations in the browser
5
5
  Author-email: Jeremy Magland <jmagland@flatironinstitute.org>
6
6
  License: Apache-2.0
@@ -72,6 +72,8 @@ Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
72
72
  Requires-Dist: lindi; extra == "docs"
73
73
  Requires-Dist: nibabel; extra == "docs"
74
74
  Requires-Dist: Pillow; extra == "docs"
75
+ Requires-Dist: scikit-learn; extra == "docs"
76
+ Requires-Dist: scipy; extra == "docs"
75
77
  Dynamic: license-file
76
78
 
77
79
  # figpack
@@ -2,7 +2,7 @@
2
2
  figpack - A Python package for creating shareable, interactive visualizations in the browser
3
3
  """
4
4
 
5
- __version__ = "0.4.2"
5
+ __version__ = "0.4.4"
6
6
 
7
7
  from .cli import view_figure
8
8
  from .core import FigpackView, FigpackExtension, ExtensionView
@@ -80,7 +80,9 @@ def prepare_figure_bundle(
80
80
  view.write_to_zarr_group(zarr_group)
81
81
 
82
82
  # Add title and description and script as attributes on the top-level zarr group
83
- zarr_group.attrs["title"] = title
83
+ # The figure title is stored separately from the root view's own "title"
84
+ # attribute (e.g. the title of a Box), so that it does not replace it
85
+ zarr_group.attrs["figure_title"] = title
84
86
  if description:
85
87
  zarr_group.attrs["description"] = description
86
88
  if script:
@@ -28,7 +28,7 @@ SPEC_SIZE_WARNING_CHARS = 20000
28
28
  _SPEC_LINE_WIDTH = 120
29
29
 
30
30
  # Props of the root node that belong to the figure rather than its root view
31
- _FIGURE_PROPS = {"title"}
31
+ _FIGURE_PROPS = {"figure_title"}
32
32
 
33
33
 
34
34
  @dataclass
@@ -40,6 +40,70 @@ class SpecProp:
40
40
  description: str
41
41
 
42
42
 
43
+ # Style keys shared by all views. A view's `style` attr holds any of them, and
44
+ # it cascades: each view's effective style is its parent view's with its own
45
+ # keys merged over it (figpack-figure/src/inheritedStyle.ts). A view ignores
46
+ # the keys it does not use and lists the ones it uses in SpecSchema.style_keys.
47
+ STYLE_KEYS: Dict[str, SpecProp] = {
48
+ p.name: p
49
+ for p in [
50
+ SpecProp("label_font_size", "number", "Axis label font size in pixels."),
51
+ SpecProp("tick_font_size", "number", "Tick label font size in pixels."),
52
+ SpecProp("legend_font_size", "number", "Legend text font size in pixels."),
53
+ SpecProp(
54
+ "font_family",
55
+ "string",
56
+ 'CSS font family of all text, e.g. "serif" or "Georgia, serif".',
57
+ ),
58
+ SpecProp(
59
+ "text_color",
60
+ "string (CSS color)",
61
+ "Main text color: axis labels, major tick labels, legend text, captions and markdown text.",
62
+ ),
63
+ SpecProp(
64
+ "background_color",
65
+ "string (CSS color)",
66
+ "Background color of plot areas (and legends), captions, markdown and the active tab.",
67
+ ),
68
+ SpecProp(
69
+ "gridline_color",
70
+ "string (CSS color)",
71
+ "Color of the major gridlines and ticks.",
72
+ ),
73
+ SpecProp(
74
+ "minor_gridline_color",
75
+ "string (CSS color)",
76
+ "Color of the minor gridlines and ticks and of the time axis line.",
77
+ ),
78
+ SpecProp(
79
+ "title_font_size",
80
+ "number",
81
+ "Font size in pixels of layout item titles and tabs (defaults 12 and 14); a layout's own title is 2 px larger. Title bars and tab bars grow to fit.",
82
+ ),
83
+ SpecProp(
84
+ "title_color",
85
+ "string (CSS color)",
86
+ "Text color of layout title bars and tabs.",
87
+ ),
88
+ SpecProp(
89
+ "title_background_color",
90
+ "string (CSS color)",
91
+ "Background color of layout title bars and tab bars.",
92
+ ),
93
+ SpecProp(
94
+ "colormap",
95
+ '"viridis" | "plasma" | "inferno" | "magma" | "gray"',
96
+ 'Colormap of heatmaps; add "_r" to reverse it (e.g. "gray_r").',
97
+ ),
98
+ SpecProp(
99
+ "border_color",
100
+ "string (CSS color)",
101
+ "Color of layout borders, splitter bars and caption borders.",
102
+ ),
103
+ ]
104
+ }
105
+
106
+
43
107
  @dataclass
44
108
  class SpecSchema:
45
109
  """
@@ -50,6 +114,7 @@ class SpecSchema:
50
114
  "views": child groups are views (e.g. layouts); each uses its own schema
51
115
  "groups": child groups are configurable parts of this view (e.g. the
52
116
  series of a TimeseriesGraph), with props given by child_props
117
+ style_keys: the keys of STYLE_KEYS that the view uses
53
118
  """
54
119
 
55
120
  view_type: str
@@ -58,6 +123,7 @@ class SpecSchema:
58
123
  children: Literal["none", "views", "groups"] = "none"
59
124
  children_description: str = ""
60
125
  child_props: List[SpecProp] = field(default_factory=list)
126
+ style_keys: List[str] = field(default_factory=list)
61
127
 
62
128
 
63
129
  def get_spec_schemas() -> Dict[str, SpecSchema]:
@@ -113,6 +179,9 @@ def _build_view_node(group, schemas: Dict[str, SpecSchema]) -> Dict[str, Any]:
113
179
  return {}
114
180
  node: Dict[str, Any] = {}
115
181
  props = _select_props(group.attrs, schema.props)
182
+ # Any view may set a style, since it cascades to the views inside it
183
+ if group.attrs.get("style"):
184
+ props["style"] = group.attrs["style"]
116
185
  if props:
117
186
  node["props"] = props
118
187
  children = {}
@@ -171,10 +240,27 @@ def build_figure_spec_docs(
171
240
  '- data: path of a group in data.zarr for this node (e.g. "/item_0"), or null for a node defined only by the spec (then view_type is required). If omitted, the node uses its counterpart in the original figure: the root group, or the parent\'s subgroup with the same name.',
172
241
  "- view_type: the view type of the node.",
173
242
  "Only the props documented here are listed in the spec. Other attributes in data.zarr describe the stored data and should not be changed.",
174
- "Root node: props.title (string) is the figure title.",
243
+ "Root node: props.figure_title (string) is the figure title.",
175
244
  ]
176
245
  view_types = _collect_view_types(zarr_group)
177
246
  documented = [vt for vt in view_types if vt in schemas]
247
+ style_users = {
248
+ key: [vt for vt in documented if key in schemas[vt].style_keys]
249
+ for key in STYLE_KEYS
250
+ }
251
+ if any(style_users.values()):
252
+ lines += [
253
+ "",
254
+ "## Style",
255
+ "",
256
+ "Any view node may set props.style to an object of the style keys below. It applies to that view and to every view inside it: styles merge key by key from the root down, and inner values win. Set it on the root node to style the whole figure. Views ignore keys they do not use.",
257
+ "Style keys:",
258
+ ]
259
+ lines += [
260
+ f"- {p.name} ({p.type}): {p.description} Used by: {', '.join(style_users[key])}."
261
+ for key, p in STYLE_KEYS.items()
262
+ if style_users[key]
263
+ ]
178
264
  for vt in documented:
179
265
  schema = schemas[vt]
180
266
  lines += ["", f"## {vt}", "", schema.description]
@@ -183,6 +269,8 @@ def build_figure_spec_docs(
183
269
  lines += [f"- {p.name} ({p.type}): {p.description}" for p in schema.props]
184
270
  else:
185
271
  lines.append("No configurable props.")
272
+ if schema.style_keys:
273
+ lines.append(f"Style keys: {', '.join(schema.style_keys)}.")
186
274
  if schema.children_description:
187
275
  lines.append(f"Children: {schema.children_description}")
188
276
  if schema.child_props:
@@ -2,11 +2,13 @@
2
2
  Base view class for figpack visualization components
3
3
  """
4
4
 
5
+ import functools
5
6
  import os
6
7
  import random
7
8
  import string
8
- from typing import TYPE_CHECKING, Optional
9
+ from typing import TYPE_CHECKING, Any, Dict, Optional
9
10
 
11
+ from ._figure_spec import STYLE_KEYS
10
12
  from .zarr import Group
11
13
 
12
14
  if TYPE_CHECKING:
@@ -18,6 +20,44 @@ class FigpackView:
18
20
  Base class for all figpack visualization components
19
21
  """
20
22
 
23
+ # Set with set_style(); replaced, never mutated, so the class default is safe
24
+ _style: Dict[str, Any] = {}
25
+
26
+ def __init_subclass__(cls, **kwargs):
27
+ super().__init_subclass__(**kwargs)
28
+ # Every view writes its style attr after its own attrs, without each
29
+ # write_to_zarr_group having to do it
30
+ write = cls.__dict__.get("write_to_zarr_group")
31
+ if write is None:
32
+ return
33
+
34
+ @functools.wraps(write)
35
+ def write_with_style(self, group: Group) -> None:
36
+ write(self, group)
37
+ if self._style:
38
+ group.attrs["style"] = dict(self._style)
39
+
40
+ cls.write_to_zarr_group = write_with_style
41
+
42
+ def set_style(self, **style: Any) -> "FigpackView":
43
+ """
44
+ Set style keys (e.g. label_font_size=16) for this view and every view
45
+ inside it. Views inside it inherit the keys unless they set their own.
46
+ Call it on the root view to style the whole figure. Keys given here are
47
+ merged over those set before.
48
+
49
+ Returns:
50
+ The view itself, so that calls can be chained
51
+ """
52
+ unknown = [k for k in style if k not in STYLE_KEYS]
53
+ if unknown:
54
+ raise ValueError(
55
+ f"Unknown style keys: {', '.join(unknown)}. "
56
+ f"Known keys: {', '.join(STYLE_KEYS)}"
57
+ )
58
+ self._style = {**self._style, **style}
59
+ return self
60
+
21
61
  def show(
22
62
  self,
23
63
  *,
@@ -0,0 +1 @@
1
+ .unitEntryBase{padding-right:13px;padding-top:10px}.unselectedUnitEntry{border:1px transparent solid;font-weight:400;color:#000;background-color:#fff}.selectedUnitEntry{border:1px solid blue;font-weight:700;color:#000;background-color:#b5d1ff}.unitIdsColumnLabelStyle{min-width:200px;font-weight:700;padding:7px 5px}.unitLabelsStyle{padding-right:3px;color:#333}.plotUnitLabel{text-align:left;margin-left:20px;margin-top:5px;font-weight:700}.plotWrapperButtonParent{display:inline-block}.plotWrapperStyleButton{padding-top:50px}.plotUnselectedStyle{border:3px solid rgb(202,224,231)}.plotSelectedStyle{border:3px solid #9999ff;background-color:#fff}.plotUnselectableStyle{border:3px solid transparent}.plotCurrentStyle{border:3px solid blue;background-color:#fff}.plotLabelStyle{font-weight:700;text-align:center;overflow:hidden;white-space:nowrap}.figpack-markdown table{border-collapse:collapse;margin:.8em 0}.figpack-markdown th,.figpack-markdown td{border:1px solid #d0d7de;padding:4px 12px}.figpack-markdown th{background-color:#7f7f7f14}.figpack-markdown blockquote{margin:.8em 0;padding:0 1em;border-left:4px solid #d0d7de;opacity:.85}.figpack-markdown math[display=block]{margin:.8em 0;font-size:1.15em}:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color:#213547;background-color:#fff;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#747bff}body{margin:0;overflow:hidden}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#f9f9f9;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.status-bar{position:fixed;left:0;right:0;height:30px;background-color:#f5f5f5;color:#333;display:flex;align-items:center;padding:0 12px;font-size:12px;border-top:1px solid #ddd;z-index:1000}.status-bar.warning{background-color:#fff3cd;color:#856404;border-top:1px solid #ffeaa7}.status-bar.error,.status-bar.expired{background-color:#f8d7da;color:#721c24;border-top:1px solid #f5c6cb}.manage-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;margin-left:12px;transition:background-color .2s}.manage-button:hover{background-color:#d0d0d0}.about-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;margin-left:12px;transition:background-color .2s}.about-button:hover{background-color:#d0d0d0}.share-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;margin-left:12px;transition:background-color .2s;display:flex;align-items:center;gap:4px}.share-button:hover{background-color:#d0d0d0}.about-dialog-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;justify-content:center;align-items:center;z-index:2000}.about-dialog{background-color:#fff;color:#333;border-radius:8px;max-width:600px;max-height:80vh;width:90%;box-shadow:0 4px 20px #00000026;overflow:hidden}.about-dialog-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #ddd}.about-dialog-header h2{margin:0;font-size:18px;font-weight:600}.about-dialog-close{background:none;border:none;color:#333;font-size:24px;cursor:pointer;padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s}.about-dialog-close:hover{background-color:#f0f0f0}.about-dialog-content{padding:20px;overflow-y:auto;max-height:calc(80vh - 80px)}.about-dialog-description{line-height:1.6}.about-dialog-provenance{margin-top:0;margin-bottom:12px;font-size:.9em;color:#555;overflow-wrap:anywhere}.about-dialog-description h1,.about-dialog-description h2,.about-dialog-description h3{margin-top:0;margin-bottom:12px}.about-dialog-description p{margin-bottom:12px}.about-dialog-description code{background-color:#f5f5f5;color:#333;padding:2px 4px;border-radius:3px;font-family:Courier New,monospace;font-size:.9em}.about-dialog-description pre{background-color:#f5f5f5;color:#333;padding:12px;border-radius:4px;overflow-x:auto;margin-bottom:12px}.about-dialog-description ul,.about-dialog-description ol{margin-bottom:12px;padding-left:20px}.about-dialog-description blockquote{border-left:4px solid #ccc;padding-left:16px;margin:12px 0;font-style:italic}.manage-edits-view{display:flex;align-items:center;margin-left:12px}.edits-notification{color:#e65100;font-weight:500;margin-right:8px}.save-changes-button{background-color:#ff9800;color:#fff;border:1px solid #f57c00;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;transition:background-color .2s}.save-changes-button:hover{background-color:#f57c00}.dialog-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;justify-content:center;align-items:center;z-index:2000}.dialog-content{background-color:#fff;color:#333;border-radius:8px;max-width:500px;width:90%;box-shadow:0 4px 20px #00000026;overflow:hidden}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #ddd}.dialog-header h3{margin:0;font-size:18px;font-weight:600}.dialog-close{background:none;border:none;color:#333;font-size:24px;cursor:pointer;padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s}.dialog-close:hover{background-color:#f0f0f0}.dialog-body{padding:20px}.dialog-body p{margin:0 0 12px;line-height:1.5}.dialog-footer{padding:16px 20px;border-top:1px solid #ddd;display:flex;justify-content:flex-end}.dialog-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc;padding:8px 16px;border-radius:4px;cursor:pointer;font-size:14px;transition:background-color .2s}.dialog-button:hover{background-color:#d0d0d0}.dialog-fullscreen{position:fixed;inset:0;background-color:#fff;color:#333;z-index:2000;display:flex;flex-direction:column}.dialog-content-fullscreen{display:flex;flex-direction:column;height:100%;overflow:hidden}.dialog-content-fullscreen .dialog-header{flex-shrink:0}.dialog-content-fullscreen .dialog-body{flex:1;overflow-y:auto;padding:20px}.dialog-content-fullscreen .dialog-footer{flex-shrink:0}