dars-framework 1.2.3__py3-none-any.whl

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 (118) hide show
  1. dars/__init__.py +0 -0
  2. dars/all.py +69 -0
  3. dars/cli/__init__.py +0 -0
  4. dars/cli/doctor/__init__.py +1 -0
  5. dars/cli/doctor/detect.py +154 -0
  6. dars/cli/doctor/doctor.py +176 -0
  7. dars/cli/doctor/installers.py +100 -0
  8. dars/cli/doctor/persist.py +62 -0
  9. dars/cli/doctor/preflight.py +33 -0
  10. dars/cli/doctor/ui.py +54 -0
  11. dars/cli/hot_reload.py +33 -0
  12. dars/cli/main.py +1107 -0
  13. dars/cli/preview.py +448 -0
  14. dars/cli/translations.py +531 -0
  15. dars/components/__init__.py +0 -0
  16. dars/components/advanced/__init__.py +8 -0
  17. dars/components/advanced/accordion.py +26 -0
  18. dars/components/advanced/card.py +33 -0
  19. dars/components/advanced/modal.py +45 -0
  20. dars/components/advanced/navbar.py +44 -0
  21. dars/components/advanced/table.py +25 -0
  22. dars/components/advanced/tabs.py +31 -0
  23. dars/components/basic/__init__.py +34 -0
  24. dars/components/basic/button.py +55 -0
  25. dars/components/basic/checkbox.py +35 -0
  26. dars/components/basic/container.py +29 -0
  27. dars/components/basic/datepicker.py +139 -0
  28. dars/components/basic/image.py +36 -0
  29. dars/components/basic/input.py +57 -0
  30. dars/components/basic/link.py +31 -0
  31. dars/components/basic/markdown.py +86 -0
  32. dars/components/basic/page.py +20 -0
  33. dars/components/basic/progressbar.py +18 -0
  34. dars/components/basic/radiobutton.py +35 -0
  35. dars/components/basic/select.py +82 -0
  36. dars/components/basic/slider.py +63 -0
  37. dars/components/basic/spinner.py +12 -0
  38. dars/components/basic/text.py +23 -0
  39. dars/components/basic/textarea.py +46 -0
  40. dars/components/basic/tooltip.py +19 -0
  41. dars/components/layout/__init__.py +0 -0
  42. dars/components/layout/anchor.py +13 -0
  43. dars/components/layout/flex.py +26 -0
  44. dars/components/layout/grid.py +45 -0
  45. dars/config.py +134 -0
  46. dars/core/__init__.py +0 -0
  47. dars/core/app.py +957 -0
  48. dars/core/component.py +284 -0
  49. dars/core/events.py +102 -0
  50. dars/core/js_bridge.py +99 -0
  51. dars/core/properties.py +127 -0
  52. dars/core/state.py +309 -0
  53. dars/dars_tests/apps_test/health_check.py +56 -0
  54. dars/dars_tests/run_tests.py +275 -0
  55. dars/dars_tests/tests/test_advanced_components.py +69 -0
  56. dars/dars_tests/tests/test_basic_components.py +88 -0
  57. dars/dars_tests/tests/test_core_and_cli.py +17 -0
  58. dars/dars_tests/tests/test_layout_components.py +58 -0
  59. dars/dars_tests/tests/test_version_check.py +21 -0
  60. dars/docs/__init__.py +0 -0
  61. dars/docs/app.md +290 -0
  62. dars/docs/cli.md +80 -0
  63. dars/docs/components.md +1679 -0
  64. dars/docs/custom_components.md +30 -0
  65. dars/docs/events.md +45 -0
  66. dars/docs/exporters.md +162 -0
  67. dars/docs/getting_started.md +79 -0
  68. dars/docs/index.md +18 -0
  69. dars/docs/scripts.md +593 -0
  70. dars/docs/state_management.md +57 -0
  71. dars/exporters/__init__.py +0 -0
  72. dars/exporters/base.py +96 -0
  73. dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
  74. dars/exporters/web/OLD/html_css_js_old.py +1406 -0
  75. dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
  76. dars/exporters/web/__init__.py +0 -0
  77. dars/exporters/web/html_css_js.py +2675 -0
  78. dars/exporters/web/vdom.py +251 -0
  79. dars/js_lib.py +206 -0
  80. dars/scripts/__init__.py +0 -0
  81. dars/scripts/dscript.py +26 -0
  82. dars/scripts/script.py +39 -0
  83. dars/security.py +195 -0
  84. dars/templates/__init__.py +0 -0
  85. dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  86. dars/templates/examples/README.md +4 -0
  87. dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
  88. dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
  89. dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
  90. dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
  91. dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
  92. dars/templates/examples/advanced/__init__.py +0 -0
  93. dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
  94. dars/templates/examples/basic/Forms/form_components.py +516 -0
  95. dars/templates/examples/basic/Forms/simple_form.py +379 -0
  96. dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
  97. dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
  98. dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
  99. dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
  100. dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
  101. dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
  102. dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
  103. dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
  104. dars/templates/examples/basic/__init__.py +0 -0
  105. dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
  106. dars/templates/examples/demo/complete_app.py +21 -0
  107. dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
  108. dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
  109. dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
  110. dars/templates/examples/markdown/__init__.py +0 -0
  111. dars/templates/html/__init__.py +0 -0
  112. dars/version.py +2 -0
  113. dars_framework-1.2.3.dist-info/METADATA +15 -0
  114. dars_framework-1.2.3.dist-info/RECORD +118 -0
  115. dars_framework-1.2.3.dist-info/WHEEL +5 -0
  116. dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
  117. dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
  118. dars_framework-1.2.3.dist-info/top_level.txt +1 -0
dars/security.py ADDED
@@ -0,0 +1,195 @@
1
+ import os
2
+ import re
3
+ from typing import Iterable, Set
4
+ from dars.core.js_bridge import esbuild_minify_js as _esbuild_minify_js, esbuild_minify_css as _esbuild_minify_css, esbuild_available as _esbuild_available
5
+
6
+ SAFE_JS_EXT = {'.js', '.mjs', '.cjs'}
7
+ SAFE_CSS_EXT = {'.css'}
8
+ SAFE_HTML_EXT = {'.html', '.htm'}
9
+
10
+ SKIP_PATTERNS = (
11
+ r'^snapshot.*\.json$',
12
+ r'^version.*\.txt$',
13
+ )
14
+
15
+ _pat_compiled = [re.compile(p) for p in SKIP_PATTERNS]
16
+
17
+
18
+ def _should_skip(filename: str) -> bool:
19
+ base = os.path.basename(filename)
20
+ for p in _pat_compiled:
21
+ if p.match(base):
22
+ return True
23
+ return False
24
+
25
+
26
+ # --- Minifiers (conservative) ---
27
+ _js_block_comments = re.compile(r"/\*.*?\*/", re.DOTALL)
28
+ _js_line_comments = re.compile(r"(^|[^:\\])//.*?$", re.MULTILINE)
29
+ _js_spaces = re.compile(r"\s+")
30
+ _js_punct_spaces = re.compile(r"\s*([{}\[\](),;:<>+=\-*/%&|^!?])\s*")
31
+
32
+ _css_comments = re.compile(r"/\*.*?\*/", re.DOTALL)
33
+ _css_spaces = re.compile(r"\s+")
34
+ _css_punct_spaces = re.compile(r"\s*([{}:;,>~+])\s*")
35
+
36
+ _html_comments = re.compile(r"<!--(?!\s*\[if).*?-->", re.DOTALL)
37
+ _html_between_tags = re.compile(r">\s+<")
38
+ _js_string_splitter = re.compile(r'(".*?"|\'.*?\'|`.*?`)', re.DOTALL)
39
+
40
+
41
+ def minify_js(src: str) -> str:
42
+ """Minify a JS source string. Uses esbuild if available; otherwise Python fallback."""
43
+ # Fast path: dump to temp file and use esbuild when available
44
+ if _esbuild_available():
45
+ try:
46
+ import tempfile
47
+ with tempfile.NamedTemporaryFile('w', delete=False, suffix='.js', encoding='utf-8') as tf_in:
48
+ tf_in.write(src)
49
+ in_path = tf_in.name
50
+ with tempfile.NamedTemporaryFile('r', delete=False, suffix='.js', encoding='utf-8') as tf_out:
51
+ out_path = tf_out.name
52
+ if _esbuild_minify_js(in_path, out_path):
53
+ try:
54
+ with open(out_path, 'r', encoding='utf-8') as fr:
55
+ return fr.read()
56
+ finally:
57
+ try: os.remove(in_path)
58
+ except Exception: pass
59
+ try: os.remove(out_path)
60
+ except Exception: pass
61
+ except Exception:
62
+ pass
63
+ # Prefer fast/robust rjsmin if available
64
+ try:
65
+ import rjsmin # type: ignore
66
+ return rjsmin.jsmin(src)
67
+ except Exception:
68
+ pass
69
+ # Conservative regex fallback
70
+ try:
71
+ s = _js_block_comments.sub("", src)
72
+ s = _js_line_comments.sub(lambda m: m.group(1), s)
73
+ parts = _js_string_splitter.split(s)
74
+ for i in range(0, len(parts), 2):
75
+ p = parts[i]
76
+ p = _js_punct_spaces.sub(r"\1", p)
77
+ p = _js_spaces.sub(" ", p)
78
+ parts[i] = p
79
+ s = "".join(parts)
80
+ return s.strip()
81
+ except Exception:
82
+ return src
83
+
84
+
85
+ def minify_css(src: str) -> str:
86
+ """Minify a CSS source string. Uses esbuild if available; otherwise Python fallback."""
87
+ if _esbuild_available():
88
+ try:
89
+ import tempfile
90
+ with tempfile.NamedTemporaryFile('w', delete=False, suffix='.css', encoding='utf-8') as tf_in:
91
+ tf_in.write(src)
92
+ in_path = tf_in.name
93
+ with tempfile.NamedTemporaryFile('r', delete=False, suffix='.css', encoding='utf-8') as tf_out:
94
+ out_path = tf_out.name
95
+ if _esbuild_minify_css(in_path, out_path):
96
+ try:
97
+ with open(out_path, 'r', encoding='utf-8') as fr:
98
+ return fr.read()
99
+ finally:
100
+ try: os.remove(in_path)
101
+ except Exception: pass
102
+ try: os.remove(out_path)
103
+ except Exception: pass
104
+ except Exception:
105
+ pass
106
+ # Prefer rcssmin if available
107
+ try:
108
+ import rcssmin # type: ignore
109
+ return rcssmin.cssmin(src)
110
+ except Exception:
111
+ pass
112
+ try:
113
+ s = _css_comments.sub("", src)
114
+ s = _css_punct_spaces.sub(r"\1", s)
115
+ s = _css_spaces.sub(" ", s)
116
+ return s.strip()
117
+ except Exception:
118
+ return src
119
+
120
+
121
+ def minify_html(src: str) -> str:
122
+ # Prefer htmlmin if available
123
+ try:
124
+ import htmlmin # type: ignore
125
+ return htmlmin.minify(src, remove_comments=True, remove_empty_space=True, reduce_boolean_attributes=True)
126
+ except Exception:
127
+ pass
128
+ try:
129
+ s = _html_comments.sub("", src)
130
+ s = re.sub(r">\s+<", "><", s)
131
+ s = re.sub(r"\s{2,}", " ", s)
132
+ return s.strip()
133
+ except Exception:
134
+ return src
135
+
136
+
137
+ def minify_output_dir(output_dir: str, extra_skip: Iterable[str] = None, progress_cb=None) -> int:
138
+ """
139
+ Minify HTML, CSS, and JS files in-place under output_dir.
140
+ Skips VDOM, snapshot, and version files by default.
141
+
142
+ Returns: number of files minified.
143
+ """
144
+ # Gather candidates first to allow accurate progress reporting
145
+ extra_skip_set: Set[str] = set(extra_skip or [])
146
+ candidates = []
147
+ for root, _dirs, files in os.walk(output_dir):
148
+ for name in files:
149
+ if name in extra_skip_set:
150
+ continue
151
+ if _should_skip(name):
152
+ continue
153
+ ext = os.path.splitext(name)[1].lower()
154
+ if ext in SAFE_JS_EXT or ext in SAFE_CSS_EXT or ext in SAFE_HTML_EXT:
155
+ candidates.append(os.path.join(root, name))
156
+
157
+ total = len(candidates)
158
+ processed = 0
159
+ written = 0
160
+ for full in candidates:
161
+ ext = os.path.splitext(full)[1].lower()
162
+ try:
163
+ with open(full, 'r', encoding='utf-8') as f:
164
+ content = f.read()
165
+ except Exception:
166
+ processed += 1
167
+ if progress_cb:
168
+ try: progress_cb(processed, total)
169
+ except Exception: pass
170
+ continue
171
+
172
+ new_content = None
173
+ if ext in SAFE_JS_EXT:
174
+ new_content = minify_js(content)
175
+ elif ext in SAFE_CSS_EXT:
176
+ new_content = minify_css(content)
177
+ elif ext in SAFE_HTML_EXT:
178
+ new_content = minify_html(content)
179
+
180
+ if new_content is not None and new_content != content:
181
+ try:
182
+ with open(full, 'w', encoding='utf-8') as f:
183
+ f.write(new_content)
184
+ written += 1
185
+ except Exception:
186
+ pass
187
+
188
+ processed += 1
189
+ if progress_cb:
190
+ try:
191
+ progress_cb(processed, total)
192
+ except Exception:
193
+ pass
194
+
195
+ return written
File without changes
@@ -0,0 +1,4 @@
1
+ # Dars - Examples
2
+
3
+ This folder contains all Dars framework templates.
4
+
@@ -0,0 +1,275 @@
1
+ from dars.all import *
2
+ from dars.scripts.script import *
3
+ # Crear la aplicación
4
+ app = App(title="Mi App con Navbar Funcional")
5
+ app.add_script(InlineScript('''
6
+ document.addEventListener('DOMContentLoaded', function() {
7
+ var modal = document.getElementById('modal-demo');
8
+ var btnAbrir = document.getElementById('btn-abrir-modal');
9
+ var btnCerrar = document.getElementById('btn-cerrar-modal');
10
+ if (modal && btnAbrir && btnCerrar) {
11
+ btnAbrir.addEventListener('click', function() {
12
+ if (modal.getAttribute('data-enabled') === 'true') {
13
+ modal.style.display = 'flex';
14
+ modal.classList.remove('dars-modal-hidden');
15
+ modal.removeAttribute('hidden');
16
+ }
17
+ });
18
+ btnCerrar.addEventListener('click', function() {
19
+ modal.style.display = 'none';
20
+ modal.classList.add('dars-modal-hidden');
21
+ modal.setAttribute('hidden', '');
22
+ });
23
+ // Ocultar modal por defecto al cargar
24
+ modal.style.display = 'none';
25
+ modal.classList.add('dars-modal-hidden');
26
+ modal.setAttribute('hidden', '');
27
+ }
28
+ });
29
+ '''))
30
+ # Función para crear el navbar (reutilizable en todas las páginas)
31
+ def crear_navbar():
32
+ home_link = Link(text="Inicio", href="/", style={"color": "white", "text-decoration": "none", "margin-right": "20px", "padding": "10px 15px", "border-radius": "5px"})
33
+ about_link = Link(text="Acerca de", href="/about.html", style={"color": "white", "text-decoration": "none", "margin-right": "20px", "padding": "10px 15px", "border-radius": "5px"})
34
+ contact_link = Link(text="Contacto", href="/contact.html", style={"color": "white", "text-decoration": "none", "margin-right": "20px", "padding": "10px 15px", "border-radius": "5px"})
35
+
36
+ return Navbar(
37
+ home_link,
38
+ about_link,
39
+ contact_link,
40
+ brand="🚀 DarsApp",
41
+ style={
42
+ "background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
43
+ "padding": "15px 30px",
44
+ "box-shadow": "0 4px 6px rgba(0,0,0,0.1)"
45
+ }
46
+ )
47
+
48
+ # PÁGINA DE INICIO
49
+ home_content = Container(
50
+ Text(
51
+ text="¡Bienvenido a DarsApp!",
52
+ style={
53
+ "font-size": "3rem",
54
+ "color": "#2c3e50",
55
+ "text-align": "center",
56
+ "margin": "50px 0 30px 0",
57
+ "font-weight": "bold"
58
+ }
59
+ ),
60
+ Text(
61
+ text="Una aplicación de demostración construida con el framework Dars",
62
+ style={
63
+ "font-size": "1.3rem",
64
+ "color": "#7f8c8d",
65
+ "text-align": "center",
66
+ "margin": "0 0 50px 0",
67
+ "line-height": "1.6"
68
+ }
69
+ ),
70
+ # --- COMPONENTES AVANZADOS DEMO ---
71
+ Card([
72
+ Text(text="Este es un Card avanzado con hijos", style={"margin-bottom": "10px"}),
73
+ Link(text="Ir a Contacto", href="/contact.html", style={"color": "#667eea"})
74
+ ], title="Demo Card", style={"margin": "30px auto", "max-width": "400px"}),
75
+
76
+ # --- Modal con botón para cerrar ---
77
+ Modal([
78
+ Text(text="¡Este es el contenido de un Modal avanzado!", style={"text-align": "center"}),
79
+ Button(text="Cerrar Modal", id="btn-cerrar-modal", style={"margin": "20px auto 0 auto", "display": "block"})
80
+ ], title="Demo Modal", is_open=False, id="modal-demo", style={"margin": "30px auto"}),
81
+ Button(
82
+ text="Mostrar Modal",
83
+ id="btn-abrir-modal",
84
+ style={"margin": "20px 0", "padding": "10px 20px", "background": "#667eea", "color": "white", "border": "none", "border-radius": "5px"}
85
+ ),
86
+
87
+ Tabs(
88
+ minimum_logic=True,
89
+ tabs=["Tab 1", "Tab 2", "Tab 3"],
90
+ panels=[
91
+ Text(text="Contenido de la pestaña 1"),
92
+ Card([Text(text="Contenido dentro de un Card en Tab 2")]),
93
+ Container(Text(text="Panel 3 con Container"))
94
+ ],
95
+ selected=0,
96
+ style={"margin": "30px auto", "max-width": "600px"}
97
+ ),
98
+
99
+ Accordion(
100
+ minimum_logic=True,
101
+ sections=[
102
+ ("Sección 1", Text(text="Contenido de la sección 1")),
103
+ ("Sección 2", Card([Text(text="Contenido de la sección 2 en Card")]))
104
+ ],
105
+ open_indices=[0],
106
+ style={"margin": "30px auto", "max-width": "600px"}
107
+ ),
108
+
109
+ Table(
110
+ columns=[
111
+ {"title": "Nombre", "field": "nombre"},
112
+ {"title": "Edad", "field": "edad"}
113
+ ],
114
+ data=[
115
+ {"nombre": "Ana", "edad": 28},
116
+ {"nombre": "Luis", "edad": 34}
117
+ ],
118
+ style={"margin": "30px auto", "max-width": "400px"}
119
+ ),
120
+ )
121
+
122
+ home_page = Page(
123
+ crear_navbar(),
124
+ home_content,
125
+ style={
126
+ "font-family": "Arial, sans-serif",
127
+ "background": "linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)",
128
+ "min-height": "100vh"
129
+ }
130
+ )
131
+
132
+ # PÁGINA ACERCA DE
133
+ about_content = Container(
134
+ Text(
135
+ text="Acerca de DarsApp",
136
+ style={
137
+ "font-size": "2.5rem",
138
+ "color": "#2c3e50",
139
+ "text-align": "center",
140
+ "margin": "50px 0 30px 0",
141
+ "font-weight": "bold"
142
+ }
143
+ ),
144
+ Text(
145
+ text="Nuestra Historia",
146
+ style={
147
+ "font-size": "1.8rem",
148
+ "color": "#34495e",
149
+ "text-align": "center",
150
+ "margin-bottom": "20px",
151
+ "font-weight": "bold"
152
+ }
153
+ ),
154
+ Text(
155
+ text="DarsApp es una aplicación de demostración que muestra las capacidades del framework Dars para crear interfaces web modernas y funcionales. Nuestro objetivo es proporcionar una experiencia de usuario excepcional a través de un diseño limpio y una navegación intuitiva.",
156
+ style={
157
+ "font-size": "1.1rem",
158
+ "line-height": "1.8",
159
+ "color": "#7f8c8d",
160
+ "text-align": "center",
161
+ "background": "white",
162
+ "padding": "40px",
163
+ "border-radius": "15px",
164
+ "box-shadow": "0 5px 15px rgba(0,0,0,0.1)",
165
+ "max-width": "800px",
166
+ "margin": "0 auto"
167
+ }
168
+ ),
169
+ style={
170
+ "max-width": "1200px",
171
+ "margin": "0 auto",
172
+ "padding": "20px"
173
+ }
174
+ )
175
+
176
+ about_page = Page(
177
+ crear_navbar(),
178
+ about_content,
179
+ style={
180
+ "font-family": "Arial, sans-serif",
181
+ "background": "linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)",
182
+ "min-height": "100vh"
183
+ }
184
+ )
185
+
186
+ # PÁGINA CONTACTO
187
+ contact_content = Container(
188
+ Text(
189
+ text="Contáctanos",
190
+ style={
191
+ "font-size": "2.5rem",
192
+ "color": "#2c3e50",
193
+ "text-align": "center",
194
+ "margin": "50px 0 30px 0",
195
+ "font-weight": "bold"
196
+ }
197
+ ),
198
+ Text(
199
+ text="📧 Información de Contacto",
200
+ style={
201
+ "font-size": "1.8rem",
202
+ "color": "#34495e",
203
+ "margin-bottom": "30px",
204
+ "font-weight": "bold",
205
+ "text-align": "center"
206
+ }
207
+ ),
208
+ Text(
209
+ text="Contact INFO",
210
+ style={
211
+ "font-size": "1.1rem",
212
+ "line-height": "2",
213
+ "color": "#7f8c8d",
214
+ "text-align": "center",
215
+ "white-space": "pre-line",
216
+ "background": "white",
217
+ "padding": "40px",
218
+ "border-radius": "15px",
219
+ "box-shadow": "0 5px 15px rgba(0,0,0,0.1)",
220
+ "max-width": "600px",
221
+ "margin": "0 auto"
222
+ }
223
+ ),
224
+ Text(
225
+ text="Horario de Atención:\nLunes a Viernes: 9:00 AM - 6:00 PM\nSábados: 10:00 AM - 2:00 PM",
226
+ style={
227
+ "font-size": "1rem",
228
+ "line-height": "1.6",
229
+ "color": "#95a5a6",
230
+ "text-align": "center",
231
+ "margin-top": "30px",
232
+ "white-space": "pre-line"
233
+ }
234
+ ),
235
+ style={
236
+ "max-width": "1200px",
237
+ "margin": "0 auto",
238
+ "padding": "20px"
239
+ }
240
+ )
241
+
242
+ contact_page = Page(
243
+ crear_navbar(),
244
+ contact_content,
245
+ style={
246
+ "font-family": "Arial, sans-serif",
247
+ "background": "linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)",
248
+ "min-height": "100vh"
249
+ }
250
+ )
251
+
252
+ # Agregar estilos globales
253
+ app.add_global_style(selector="body", styles={
254
+ "margin": "0",
255
+ "padding": "0",
256
+ "font-family": "Arial, sans-serif"
257
+ })
258
+
259
+ app.add_global_style(selector="a", styles={
260
+ "transition": "all 0.3s ease"
261
+ })
262
+
263
+ app.add_global_style(selector="a:hover", styles={
264
+ "background-color": "rgba(255,255,255,0.2) !important",
265
+ "transform": "translateY(-2px)"
266
+ })
267
+
268
+ # Agregar todas las páginas a la aplicación
269
+ app.add_page(name="index", root=home_page, index=True)
270
+ app.add_page(name="about", root=about_page)
271
+ app.add_page(name="contact", root=contact_page)
272
+
273
+ if __name__ == "__main__":
274
+ app.rTimeCompile() # Preview en vivo
275
+