From e22ad21fd0979692696a0add92b3cbdf32f61d42 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:07:56 +0300 Subject: [PATCH] lua: select, log, body, on, get_text, set_text, subscribtions, events (keyup, keydown, keypress, mouseover, mouseup, mousedown, mouseenter, mouseexit, click, focusin, focusout) --- flumi/Scripts/B9/HTMLParser.gd | 30 ++ flumi/Scripts/B9/Lua.gd | 371 ++++++++++++++++++ flumi/Scripts/B9/Lua.gd.uid | 1 + flumi/Scripts/Constants.gd | 111 +++++- flumi/Scripts/Utils/Lua/Event.gd | 161 ++++++++ flumi/Scripts/Utils/Lua/Event.gd.uid | 1 + flumi/Scripts/Utils/Lua/Function.gd | 57 +++ flumi/Scripts/Utils/Lua/Function.gd.uid | 1 + flumi/Scripts/Utils/Lua/Print.gd | 87 ++++ flumi/Scripts/Utils/Lua/Print.gd.uid | 1 + flumi/Scripts/Utils/LuaEventUtils.gd.uid | 1 + flumi/Scripts/Utils/LuaFunctionUtils.gd.uid | 1 + flumi/Scripts/Utils/LuaPrintUtils.gd.uid | 1 + flumi/Scripts/main.gd | 31 +- flumi/addons/gdluau/LICENSE | 21 + flumi/addons/gdluau/bin/linux/.gitkeep | 0 .../libgdluau.linux.template_debug.x86_64.so | Bin 0 -> 5619024 bytes ...libgdluau.linux.template_release.x86_64.so | Bin 0 -> 6277792 bytes flumi/addons/gdluau/bin/macos/.gitkeep | 0 .../libgdluau.macos.template_debug | Bin 0 -> 2266312 bytes .../libgdluau.macos.template_release | Bin 0 -> 2282680 bytes flumi/addons/gdluau/bin/windows/.gitkeep | 0 .../gdluau.windows.template_debug.x86_64.dll | Bin 0 -> 1184768 bytes ...gdluau.windows.template_release.x86_64.dll | Bin 0 -> 1081856 bytes .../gdluau/bin/windows/libgcc_s_seh-1.dll | Bin 0 -> 107187 bytes .../addons/gdluau/bin/windows/libstdc++-6.dll | Bin 0 -> 2025890 bytes .../gdluau/bin/windows/libwinpthread-1.dll | Bin 0 -> 60356 bytes .../~gdluau.windows.template_debug.x86_64.dll | Bin 0 -> 1184768 bytes flumi/addons/gdluau/gdluau.gdextension | 13 + flumi/addons/gdluau/gdluau.gdextension.uid | 1 + ...windows.template_release.double.x86_64.dll | Bin 0 -> 421376 bytes 31 files changed, 879 insertions(+), 11 deletions(-) create mode 100644 flumi/Scripts/B9/Lua.gd create mode 100644 flumi/Scripts/B9/Lua.gd.uid create mode 100644 flumi/Scripts/Utils/Lua/Event.gd create mode 100644 flumi/Scripts/Utils/Lua/Event.gd.uid create mode 100644 flumi/Scripts/Utils/Lua/Function.gd create mode 100644 flumi/Scripts/Utils/Lua/Function.gd.uid create mode 100644 flumi/Scripts/Utils/Lua/Print.gd create mode 100644 flumi/Scripts/Utils/Lua/Print.gd.uid create mode 100644 flumi/Scripts/Utils/LuaEventUtils.gd.uid create mode 100644 flumi/Scripts/Utils/LuaFunctionUtils.gd.uid create mode 100644 flumi/Scripts/Utils/LuaPrintUtils.gd.uid create mode 100644 flumi/addons/gdluau/LICENSE create mode 100644 flumi/addons/gdluau/bin/linux/.gitkeep create mode 100644 flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so create mode 100644 flumi/addons/gdluau/bin/linux/libgdluau.linux.template_release.x86_64.so create mode 100644 flumi/addons/gdluau/bin/macos/.gitkeep create mode 100644 flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_debug create mode 100644 flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_release create mode 100644 flumi/addons/gdluau/bin/windows/.gitkeep create mode 100644 flumi/addons/gdluau/bin/windows/gdluau.windows.template_debug.x86_64.dll create mode 100644 flumi/addons/gdluau/bin/windows/gdluau.windows.template_release.x86_64.dll create mode 100644 flumi/addons/gdluau/bin/windows/libgcc_s_seh-1.dll create mode 100644 flumi/addons/gdluau/bin/windows/libstdc++-6.dll create mode 100644 flumi/addons/gdluau/bin/windows/libwinpthread-1.dll create mode 100644 flumi/addons/gdluau/bin/windows/~gdluau.windows.template_debug.x86_64.dll create mode 100644 flumi/addons/gdluau/gdluau.gdextension create mode 100644 flumi/addons/gdluau/gdluau.gdextension.uid create mode 100644 flumi/addons/godot-flexbox/bin/windows/~godot-flexbox.windows.template_release.double.x86_64.dll diff --git a/flumi/Scripts/B9/HTMLParser.gd b/flumi/Scripts/B9/HTMLParser.gd index 8911e73..edd3535 100644 --- a/flumi/Scripts/B9/HTMLParser.gd +++ b/flumi/Scripts/B9/HTMLParser.gd @@ -43,12 +43,19 @@ class HTMLElement: func is_inline_element() -> bool: return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a", "input"] +class HTMLBody extends HTMLElement: + var body_node: Node = null + + func _init(): + super._init("body") + class ParseResult: var root: HTMLElement var all_elements: Array[HTMLElement] = [] var errors: Array[String] = [] var css_parser: CSSParser = null var inline_styles: Dictionary = {} + var dom_nodes: Dictionary = {} func _init(): root = HTMLElement.new("document") @@ -271,6 +278,11 @@ func find_by_id(element_id: String) -> HTMLElement: return null +func register_dom_node(element: HTMLElement, node: Control) -> void: + var element_id = element.get_id() + if element_id.length() > 0: + parse_result.dom_nodes[element_id] = node + func find_first(tag: String, attribute: String = "") -> HTMLElement: var results = find_all(tag, attribute) return results[0] if results.size() > 0 else null @@ -334,6 +346,24 @@ func get_all_images() -> Array[String]: func get_all_scripts() -> Array[String]: return get_attribute_values("script", "src") +func process_scripts(lua_api: LuaAPI, lua_vm) -> void: + if not lua_api or not lua_vm: + print("Warning: Lua API or VM not available for script processing") + return + + lua_api.dom_parser = self + + for script_element in find_all("script"): + var src = script_element.get_attribute("src") + var inline_code = script_element.text_content.strip_edges() + + if not src.is_empty(): + # TODO: add support for external Lua script + print("External script found: ", src) + elif not inline_code.is_empty(): + print("Executing inline Lua script") + lua_api.execute_lua_script(inline_code, lua_vm) + func get_all_stylesheets() -> Array[String]: return get_attribute_values("style", "src") diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd new file mode 100644 index 0000000..b660250 --- /dev/null +++ b/flumi/Scripts/B9/Lua.gd @@ -0,0 +1,371 @@ +class_name LuaAPI +extends Node + +class EventSubscription: + var id: int + var element_id: String + var event_name: String + var callback_ref: int + var vm: LuauVM + var lua_api: LuaAPI + var connected_signal: String = "" + var connected_node: Node = null + +var dom_parser: HTMLParser +var event_subscriptions: Dictionary = {} +var next_subscription_id: int = 1 +var next_callback_ref: int = 1 + +func _gurt_select_handler(vm: LuauVM) -> int: + var selector: String = vm.luaL_checkstring(1) + + var element_id = "" + if selector.begins_with("#"): + element_id = selector.substr(1) + else: + vm.lua_pushnil() + return 1 + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if not dom_node: + vm.lua_pushnil() + return 1 + + vm.lua_newtable() + vm.lua_pushstring(element_id) + vm.lua_setfield(-2, "_element_id") + + add_element_methods(vm) + return 1 + +func add_element_methods(vm: LuauVM) -> void: + vm.lua_pushcallable(_element_set_text_handler, "element.set_text") + vm.lua_setfield(-2, "set_text") + + vm.lua_pushcallable(_element_get_text_handler, "element.get_text") + vm.lua_setfield(-2, "get_text") + + vm.lua_pushcallable(_element_on_event_handler, "element.on") + vm.lua_setfield(-2, "on") + +# Element manipulation handlers +func _element_set_text_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var text: String = vm.luaL_checkstring(2) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + var text_node = get_dom_node(dom_node, "text") + if text_node: + text_node.text = text + return 0 + +func _element_get_text_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + var text = "" + + var text_node = get_dom_node(dom_node, "text") + if text_node: + if text_node.has_method("get_text"): + text = text_node.get_text() + else: + text = text_node.text + + vm.lua_pushstring(text) + return 1 + +# Event system handlers +func _element_on_event_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var event_name: String = vm.luaL_checkstring(2) + vm.luaL_checktype(3, vm.LUA_TFUNCTION) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if not dom_node: + vm.lua_pushnil() + return 1 + + var subscription = _create_subscription(vm, element_id, event_name) + event_subscriptions[subscription.id] = subscription + + var signal_node = get_dom_node(dom_node, "signal") + var success = LuaEventUtils.connect_element_event(signal_node, event_name, subscription) + + return _handle_subscription_result(vm, subscription, success) + +func _body_on_event_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var event_name: String = vm.luaL_checkstring(2) + vm.luaL_checktype(3, vm.LUA_TFUNCTION) + + var subscription = _create_subscription(vm, "body", event_name) + event_subscriptions[subscription.id] = subscription + + var success = LuaEventUtils.connect_body_event(event_name, subscription, self) + + return _handle_subscription_result(vm, subscription, success) + +func _subscription_unsubscribe_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + + vm.lua_getfield(1, "_subscription_id") + var subscription_id: int = vm.lua_tointeger(-1) + vm.lua_pop(1) + + var subscription = event_subscriptions.get(subscription_id, null) + if subscription: + LuaEventUtils.disconnect_subscription(subscription, self) + event_subscriptions.erase(subscription_id) + vm.lua_pushnil() + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, subscription.callback_ref) + + return 0 + +# Subscription management +func _create_subscription(vm: LuauVM, element_id: String, event_name: String) -> EventSubscription: + var subscription_id = next_subscription_id + next_subscription_id += 1 + var callback_ref = next_callback_ref + next_callback_ref += 1 + + vm.lua_pushvalue(3) + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, callback_ref) + + var subscription = EventSubscription.new() + subscription.id = subscription_id + subscription.element_id = element_id + subscription.event_name = event_name + subscription.callback_ref = callback_ref + subscription.vm = vm + subscription.lua_api = self + + return subscription + +func _handle_subscription_result(vm: LuauVM, subscription: EventSubscription, success: bool) -> int: + if success: + vm.lua_newtable() + vm.lua_pushinteger(subscription.id) + vm.lua_setfield(-2, "_subscription_id") + + vm.lua_pushcallable(_subscription_unsubscribe_handler, "subscription.unsubscribe") + vm.lua_setfield(-2, "unsubscribe") + + return 1 + else: + vm.lua_pushnil() + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, subscription.callback_ref) + event_subscriptions.erase(subscription.id) + vm.lua_pushnil() + return 1 + +# Event callbacks +func _on_event_triggered(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref) + if subscription.vm.lua_isfunction(-1): + if subscription.vm.lua_pcall(0, 0, 0) != subscription.vm.LUA_OK: + print("GURT ERROR in event callback: ", subscription.vm.lua_tostring(-1)) + subscription.vm.lua_pop(1) + else: + subscription.vm.lua_pop(1) + +func _on_gui_input_click(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: + _execute_lua_callback(subscription) + +func _on_gui_input_mouse_universal(event: InputEvent, signal_node: Node) -> void: + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT: + # Find all subscriptions for this node with mouse events + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.connected_node == signal_node and subscription.connected_signal == "gui_input_mouse": + var should_trigger = false + if subscription.event_name == "mousedown" and mouse_event.pressed: + should_trigger = true + elif subscription.event_name == "mouseup" and not mouse_event.pressed: + should_trigger = true + + if should_trigger: + _execute_lua_callback(subscription) + +# Event callback handlers +func _on_gui_input_mousemove(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseMotion: + var mouse_event = event as InputEventMouseMotion + _handle_mousemove_event(mouse_event, subscription) + +func _on_focus_gui_input(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: + if subscription.event_name == "focusin": + _execute_lua_callback(subscription) + +func _on_body_mouse_enter(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if subscription.event_name == "mouseenter": + _execute_lua_callback(subscription) + +func _on_body_mouse_exit(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if subscription.event_name == "mouseexit": + _execute_lua_callback(subscription) + +func _execute_lua_callback(subscription: EventSubscription, args: Array = []) -> void: + subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref) + if subscription.vm.lua_isfunction(-1): + for arg in args: + subscription.vm.lua_pushvariant(arg) + + if subscription.vm.lua_pcall(args.size(), 0, 0) != subscription.vm.LUA_OK: + print("GURT ERROR in callback: ", subscription.vm.lua_tostring(-1)) + subscription.vm.lua_pop(1) + else: + subscription.vm.lua_pop(1) + +# Global input processing +func _input(event: InputEvent) -> void: + if event is InputEventKey: + var key_event = event as InputEventKey + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.element_id == "body" and subscription.connected_signal == "input": + var should_trigger = false + match subscription.event_name: + "keydown": + should_trigger = key_event.pressed + "keyup": + should_trigger = not key_event.pressed + "keypress": + should_trigger = key_event.pressed + + if should_trigger: + var key_info = { + "key": OS.get_keycode_string(key_event.keycode), + "keycode": key_event.keycode, + "ctrl": key_event.ctrl_pressed, + "shift": key_event.shift_pressed, + "alt": key_event.alt_pressed + } + _execute_lua_callback(subscription, [key_info]) + + elif event is InputEventMouseMotion: + var mouse_event = event as InputEventMouseMotion + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.element_id == "body" and subscription.connected_signal == "input_mousemove": + if subscription.event_name == "mousemove": + _handle_mousemove_event(mouse_event, subscription) + +func _handle_mousemove_event(mouse_event: InputEventMouseMotion, subscription: EventSubscription) -> void: + # TODO: pass reference instead of hardcoded path + var body_container = get_node("/root/Main").website_container + + if body_container.get_parent() is MarginContainer: + body_container = body_container.get_parent() + + if not body_container: + return + + var container_rect = body_container.get_global_rect() + var local_x = mouse_event.global_position.x - container_rect.position.x + var local_y = mouse_event.global_position.y - container_rect.position.y + + # Only provide coordinates if mouse is within the container bounds + if local_x >= 0 and local_y >= 0 and local_x <= container_rect.size.x and local_y <= container_rect.size.y: + var mouse_info = { + "x": local_x, + "y": local_y, + "deltaX": mouse_event.relative.x, + "deltaY": mouse_event.relative.y + } + _execute_lua_callback(subscription, [mouse_info]) + +# DOM node utilities +func get_dom_node(node: Node, purpose: String = "general") -> Node: + if not node: + return null + + if node is MarginContainer: + node = node.get_child(0) + + match purpose: + "signal": + if node is HTMLButton: + return node.get_node_or_null("ButtonNode") + elif node is RichTextLabel: + return node + elif node.has_method("get") and node.get("rich_text_label"): + return node.get("rich_text_label") + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + return node + "text": + if node.has_method("set_text") and node.has_method("get_text"): + return node + elif node is RichTextLabel: + return node + elif node.has_method("get") and node.get("rich_text_label"): + return node.get("rich_text_label") + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + if "text" in node: + return node + return null + "general": + if node is HTMLButton: + return node.get_node_or_null("ButtonNode") + elif node is RichTextLabel: + return node + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + return node + + return node + +# Main execution function +func execute_lua_script(code: String, vm: LuauVM): + vm.open_libraries([vm.LUA_BASE_LIB, vm.LUA_BIT32_LIB, + vm.LUA_COROUTINE_LIB, vm.LUA_MATH_LIB, vm.LUA_UTF8_LIB, + vm.LUA_TABLE_LIB, vm.LUA_STRING_LIB, vm.LUA_VECTOR_LIB]) + + LuaFunctionUtils.setup_gurt_api(vm, self, dom_parser) + + if vm.lua_dostring(code) != vm.LUA_OK: + print("LUA ERROR: ", vm.lua_tostring(-1)) + vm.lua_pop(1) diff --git a/flumi/Scripts/B9/Lua.gd.uid b/flumi/Scripts/B9/Lua.gd.uid new file mode 100644 index 0000000..cd3c752 --- /dev/null +++ b/flumi/Scripts/B9/Lua.gd.uid @@ -0,0 +1 @@ +uid://biv2ch1mi3lnn diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index e5a5a64..9d20b85 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -100,7 +100,7 @@ var HTML_CONTENT2 = """
""".to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENTbv = """This paragraph should be red and bold (h1 + p)
-This paragraph should be blue (h1 ~ p)
+This paragraph should be blue (h1 ~ p)
This paragraph should be purple and bold (div p and .outer-div > p)
This paragraph should be purple but not bold (div p only)
@@ -452,20 +451,20 @@ var HTML_CONTENT_S = """ This span should have light red bg (h3 ~ span) This span should also have light red bg (h3 ~ span) -Regular paragraph in container
This page demonstrates the GURT Lua API in action.
+ +Click the button
+ +Move your mouse
+ +Move mouse over Button
+ +Type something
+ +""".to_utf8_buffer() diff --git a/flumi/Scripts/Utils/Lua/Event.gd b/flumi/Scripts/Utils/Lua/Event.gd new file mode 100644 index 0000000..d794a8f --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Event.gd @@ -0,0 +1,161 @@ +class_name LuaEventUtils +extends RefCounted + +static func connect_element_event(signal_node: Node, event_name: String, subscription) -> bool: + if not signal_node: + return false + + match event_name: + "click": + if signal_node.has_signal("pressed"): + signal_node.pressed.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "pressed" + subscription.connected_node = signal_node if signal_node != subscription.lua_api.get_dom_node(signal_node.get_parent(), "signal") else null + return true + elif signal_node is Control: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_click.bind(subscription)) + subscription.connected_signal = "gui_input" + subscription.connected_node = signal_node + return true + "mousedown", "mouseup": + if signal_node is Control: + # Check if we already have a mouse handler connected to this node + var already_connected = false + for existing_id in subscription.lua_api.event_subscriptions: + var existing_sub = subscription.lua_api.event_subscriptions[existing_id] + if existing_sub.connected_node == signal_node and existing_sub.connected_signal == "gui_input_mouse": + already_connected = true + break + + if not already_connected: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_mouse_universal.bind(signal_node)) + + subscription.connected_signal = "gui_input_mouse" + subscription.connected_node = signal_node + return true + "mousemove": + if signal_node is Control: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_mousemove.bind(subscription)) + subscription.connected_signal = "gui_input_mousemove" + subscription.connected_node = signal_node + return true + "mouseenter": + if signal_node is Control and signal_node.has_signal("mouse_entered"): + signal_node.mouse_entered.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "mouse_entered" + subscription.connected_node = signal_node + return true + "mouseexit": + if signal_node is Control and signal_node.has_signal("mouse_exited"): + signal_node.mouse_exited.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "mouse_exited" + subscription.connected_node = signal_node + return true + "focusin": + if signal_node is Control: + signal_node.focus_mode = Control.FOCUS_ALL + if signal_node.has_signal("focus_entered"): + signal_node.focus_entered.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "focus_entered" + subscription.connected_node = signal_node + return true + else: + signal_node.gui_input.connect(subscription.lua_api._on_focus_gui_input.bind(subscription)) + subscription.connected_signal = "gui_input_focus" + subscription.connected_node = signal_node + return true + "focusout": + if signal_node is Control and signal_node.has_signal("focus_exited"): + signal_node.focus_exited.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "focus_exited" + subscription.connected_node = signal_node + return true + + return false + +static func connect_body_event(event_name: String, subscription, lua_api) -> bool: + match event_name: + "keydown", "keypress", "keyup": + lua_api.set_process_input(true) + subscription.connected_signal = "input" + subscription.connected_node = lua_api + return true + "mousemove": + lua_api.set_process_input(true) + subscription.connected_signal = "input_mousemove" + subscription.connected_node = lua_api + return true + "mouseenter", "mouseexit": + var main_container = lua_api.dom_parser.parse_result.dom_nodes.get("body", null) + if main_container: + if event_name == "mouseenter": + main_container.mouse_entered.connect(lua_api._on_body_mouse_enter.bind(subscription)) + subscription.connected_signal = "mouse_entered" + elif event_name == "mouseexit": + main_container.mouse_exited.connect(lua_api._on_body_mouse_exit.bind(subscription)) + subscription.connected_signal = "mouse_exited" + subscription.connected_node = main_container + return true + "focusin", "focusout": + subscription.connected_signal = "focus_events" + subscription.connected_node = lua_api + return true + + return false + +static func _count_active_input_subscriptions(lua_api) -> int: + var count = 0 + for sub_id in lua_api.event_subscriptions: + var sub = lua_api.event_subscriptions[sub_id] + if sub.connected_signal in ["input", "input_mousemove"]: + count += 1 + return count + +static func disconnect_subscription(subscription, lua_api) -> void: + var target_node = subscription.connected_node if subscription.connected_node else lua_api.dom_parser.parse_result.dom_nodes.get(subscription.element_id, null) + + if target_node and subscription.connected_signal: + match subscription.connected_signal: + "pressed": + if target_node.has_signal("pressed"): + target_node.pressed.disconnect(lua_api._on_event_triggered.bind(subscription)) + "gui_input": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_click.bind(subscription)) + "gui_input_mouse": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_mouse_universal.bind(target_node)) + "gui_input_mousemove": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_mousemove.bind(subscription)) + "gui_input_focus": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_focus_gui_input.bind(subscription)) + "mouse_entered": + if target_node.has_signal("mouse_entered"): + # Check if this is a body event or element event + if subscription.element_id == "body": + target_node.mouse_entered.disconnect(lua_api._on_body_mouse_enter.bind(subscription)) + else: + target_node.mouse_entered.disconnect(lua_api._on_event_triggered.bind(subscription)) + "mouse_exited": + if target_node.has_signal("mouse_exited"): + # Check if this is a body event or element event + if subscription.element_id == "body": + target_node.mouse_exited.disconnect(lua_api._on_body_mouse_exit.bind(subscription)) + else: + target_node.mouse_exited.disconnect(lua_api._on_event_triggered.bind(subscription)) + "focus_entered": + if target_node.has_signal("focus_entered"): + target_node.focus_entered.disconnect(lua_api._on_event_triggered.bind(subscription)) + "focus_exited": + if target_node.has_signal("focus_exited"): + target_node.focus_exited.disconnect(lua_api._on_event_triggered.bind(subscription)) + "input": + # Only disable input processing if no other input subscriptions remain + if _count_active_input_subscriptions(lua_api) <= 1: + lua_api.set_process_input(false) + "input_mousemove": + # Only disable input processing if no other input subscriptions remain + if _count_active_input_subscriptions(lua_api) <= 1: + lua_api.set_process_input(false) diff --git a/flumi/Scripts/Utils/Lua/Event.gd.uid b/flumi/Scripts/Utils/Lua/Event.gd.uid new file mode 100644 index 0000000..f682a24 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Event.gd.uid @@ -0,0 +1 @@ +uid://b7bck02xxt0u6 diff --git a/flumi/Scripts/Utils/Lua/Function.gd b/flumi/Scripts/Utils/Lua/Function.gd new file mode 100644 index 0000000..a0898c1 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Function.gd @@ -0,0 +1,57 @@ +class_name LuaFunctionUtils +extends RefCounted + +# Core Lua handler functions that extend Lua functionality + +static func table_tostring_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var table_string = LuaPrintUtils.table_to_string(vm, 1) + vm.lua_pushstring(table_string) + return 1 + +static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void: + # override global print + # This makes print() behave like gurt.log() + vm.lua_pushcallable(LuaPrintUtils.lua_print, "print") + vm.lua_setglobal("print") + + # Add table.tostring utility + vm.lua_getglobal("table") + if vm.lua_isnil(-1): + vm.lua_pop(1) + vm.lua_newtable() + vm.lua_setglobal("table") + vm.lua_getglobal("table") + + vm.lua_pushcallable(LuaFunctionUtils.table_tostring_handler, "table.tostring") + vm.lua_setfield(-2, "tostring") + vm.lua_pop(1) # Pop table from stack + + vm.lua_newtable() + + vm.lua_pushcallable(LuaPrintUtils.lua_print, "gurt.log") + vm.lua_setfield(-2, "log") + + vm.lua_pushcallable(lua_api._gurt_select_handler, "gurt.select") + vm.lua_setfield(-2, "select") + + # Add body element access + var body_element = dom_parser.find_first("body") + if body_element: + vm.lua_newtable() + vm.lua_pushstring("body") + vm.lua_setfield(-2, "_element_id") + + # NOTE: same code as add_element_methods, but lazy to handle body.xxxx prop + vm.lua_pushcallable(lua_api._element_set_text_handler, "body.set_text") + vm.lua_setfield(-2, "set_text") + + vm.lua_pushcallable(lua_api._element_get_text_handler, "body.get_text") + vm.lua_setfield(-2, "get_text") + + vm.lua_pushcallable(lua_api._body_on_event_handler, "body.on") + vm.lua_setfield(-2, "on") + + vm.lua_setfield(-2, "body") + + vm.lua_setglobal("gurt") diff --git a/flumi/Scripts/Utils/Lua/Function.gd.uid b/flumi/Scripts/Utils/Lua/Function.gd.uid new file mode 100644 index 0000000..b63974c --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Function.gd.uid @@ -0,0 +1 @@ +uid://dbsrdfnhv4h16 diff --git a/flumi/Scripts/Utils/Lua/Print.gd b/flumi/Scripts/Utils/Lua/Print.gd new file mode 100644 index 0000000..631528a --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Print.gd @@ -0,0 +1,87 @@ +class_name LuaPrintUtils +extends RefCounted + +static func lua_print(vm: LuauVM) -> int: + var message_parts: Array[String] = [] + var num_args: int = vm.lua_gettop() + + for i in range(1, num_args + 1): + var value_str = lua_value_to_string(vm, i) + message_parts.append(value_str) + + var final_message = "\t".join(message_parts) + print("GURT LOG: ", final_message) + return 0 + +static func lua_value_to_string(vm: LuauVM, index: int) -> String: + var lua_type = vm.lua_type(index) + + match lua_type: + vm.LUA_TNIL: + return "nil" + vm.LUA_TBOOLEAN: + return "true" if vm.lua_toboolean(index) else "false" + vm.LUA_TNUMBER: + return str(vm.lua_tonumber(index)) + vm.LUA_TSTRING: + return vm.lua_tostring(index) + vm.LUA_TTABLE: + return table_to_string(vm, index) + vm.LUA_TFUNCTION: + return "[function]" + vm.LUA_TUSERDATA: + return "[userdata]" + vm.LUA_TVECTOR: + var vec = vm.lua_tovector(index) + return "vector(" + str(vec.x) + ", " + str(vec.y) + ", " + str(vec.z) + ", " + str(vec.w) + ")" + _: + return "[" + vm.lua_typename(lua_type) + "]" + +static func table_to_string(vm: LuauVM, index: int, max_depth: int = 3, current_depth: int = 0) -> String: + if current_depth >= max_depth: + return "{...}" + + var result = "{" + var first = true + var count = 0 + var max_items = 10 + + # Convert negative index to positive + if index < 0: + index = vm.lua_gettop() + index + 1 + + # Iterate through table + vm.lua_pushnil() # First key + while vm.lua_next(index): + if count >= max_items: + # We need to pop the value and key before breaking + vm.lua_pop(2) # Remove value and key + break + + if not first: + result += ", " + first = false + + # Get key + var key_str = lua_value_to_string(vm, -2) + # Get value + var value_str = "" + if vm.lua_type(-1) == vm.LUA_TTABLE: + value_str = table_to_string(vm, -1, max_depth, current_depth + 1) + else: + value_str = lua_value_to_string(vm, -1) + + # Check if key is a valid identifier (for shorthand) + if key_str.is_valid_identifier(): + result += key_str + ": " + value_str + else: + result += "[" + key_str + "]: " + value_str + + vm.lua_pop(1) # Remove value, keep key for next iteration + count += 1 + + if count >= max_items: + result += ", ..." + + result += "}" + return result \ No newline at end of file diff --git a/flumi/Scripts/Utils/Lua/Print.gd.uid b/flumi/Scripts/Utils/Lua/Print.gd.uid new file mode 100644 index 0000000..85d0c4d --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Print.gd.uid @@ -0,0 +1 @@ +uid://dvdnveday3ev1 diff --git a/flumi/Scripts/Utils/LuaEventUtils.gd.uid b/flumi/Scripts/Utils/LuaEventUtils.gd.uid new file mode 100644 index 0000000..4b9aef4 --- /dev/null +++ b/flumi/Scripts/Utils/LuaEventUtils.gd.uid @@ -0,0 +1 @@ +uid://bt0njoa0olpb7 diff --git a/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid b/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid new file mode 100644 index 0000000..a5f3901 --- /dev/null +++ b/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid @@ -0,0 +1 @@ +uid://dxti5hc236dcp diff --git a/flumi/Scripts/Utils/LuaPrintUtils.gd.uid b/flumi/Scripts/Utils/LuaPrintUtils.gd.uid new file mode 100644 index 0000000..50e2f05 --- /dev/null +++ b/flumi/Scripts/Utils/LuaPrintUtils.gd.uid @@ -0,0 +1 @@ +uid://bwbbe8r7u10ov diff --git a/flumi/Scripts/main.gd b/flumi/Scripts/main.gd index c58182f..a40e087 100644 --- a/flumi/Scripts/main.gd +++ b/flumi/Scripts/main.gd @@ -99,7 +99,18 @@ func render() -> void: if body: StyleManager.apply_body_styles(body, parser, website_container, website_background) + + parser.register_dom_node(body, website_container) + var scripts = parser.find_all("script") + var lua_vm = null + var lua_api = null + if scripts.size() > 0: + lua_vm = LuauVM.new() + lua_api = LuaAPI.new() + add_child(lua_vm) + add_child(lua_api) + var i = 0 while i < body.children.size(): var element: HTMLParser.HTMLElement = body.children[i] @@ -118,6 +129,8 @@ func render() -> void: for inline_element in inline_elements: var inline_node = await create_element_node(inline_element, parser) if inline_node: + parser.register_dom_node(inline_element, inline_node) + safe_add_child(hbox, inline_node) # Handle hyperlinks for all inline elements if contains_hyperlink(inline_element) and inline_node is RichTextLabel: @@ -130,6 +143,8 @@ func render() -> void: var element_node = await create_element_node(element, parser) if element_node: + parser.register_dom_node(element, element_node) + # ul/ol handle their own adding if element.tag_name != "ul" and element.tag_name != "ol": safe_add_child(website_container, element_node) @@ -144,6 +159,9 @@ func render() -> void: print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name) i += 1 + + if scripts.size() > 0 and lua_vm and lua_api: + parser.process_scripts(lua_api, lua_vm) static func safe_add_child(parent: Node, child: Node) -> void: if child.get_parent(): @@ -232,7 +250,15 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> # Apply flex CONTAINER properties if it's a flex container if is_flex_container: - StyleManager.apply_flex_container_properties(final_node, styles) + var flex_container_node = final_node + # If the node was wrapped in a MarginContainer, get the inner FlexContainer + if final_node is MarginContainer and final_node.get_child_count() > 0: + var first_child = final_node.get_child(0) + if first_child is FlexContainer: + flex_container_node = first_child + + if flex_container_node is FlexContainer: + StyleManager.apply_flex_container_properties(flex_container_node, styles) # Apply flex ITEM properties StyleManager.apply_flex_item_properties(final_node, styles) @@ -252,6 +278,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> if not child_element.is_inline_element() or is_flex_container: var child_node = await create_element_node(child_element, parser) if child_node and is_instance_valid(container_for_children): + parser.register_dom_node(child_element, child_node) safe_add_child(container_for_children, child_node) return final_node @@ -340,7 +367,7 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP node = BackgroundUtils.create_panel_container_with_background(styles, hover_styles) else: node = DIV.instantiate() - node.init(element, parser) + node.init(element) var has_only_text = is_text_only_element(element) diff --git a/flumi/addons/gdluau/LICENSE b/flumi/addons/gdluau/LICENSE new file mode 100644 index 0000000..667db1f --- /dev/null +++ b/flumi/addons/gdluau/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Tigran Mamedov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/flumi/addons/gdluau/bin/linux/.gitkeep b/flumi/addons/gdluau/bin/linux/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so b/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so new file mode 100644 index 0000000000000000000000000000000000000000..3c4f478c24a9bc48a55fe190daa01d23b8baadd4 GIT binary patch literal 5619024 zcmeF)34kMInLqwwKn#LVAV)Y{gQ7+Rte!KYtJciWkl~mqX9mn7Zqn1;Gim6f=~xRvXyg8!$?w;K4CE4cj}_}g0Ce;wSHIve13 ziTmK+5pROuD;|K~C*B5kWgK?E&6^I2=BM$M?(yu1$H46$^LiZzFG>Fi@QU=41W(j? zx)!)2{oCL*=|7F0ps(|!n*&}K&wzW9CpqxqJNR|w!E53!xc^RWUjX;Si{R#eaQhOt z^KNzz+!rr{d*T&vQ@jf9zl-~?fg8v3e47JzqaR0sR32?K@o& dpCbijS_47ewrLzmZ;2Y19>a8tYh?!Sj$ZxP%RFM<2AUU=Y+v@e63;uUaD`l*6D z;x+J! 4Q`2dz}xb=jGMd1e@#3F zo|Aq|@Vc~*gF7;RlHd)=XA8XkJI t_-glf*8GowYyO+yn*VWd&Hn_r=6@1g^WOs3{7->v z{@dW1|7mc|e+OLiKLf7$p99zY&x33JyXczd;F|wMaLxY`xaPkHuK8aE*Zi-5YyMZk zHUDein*Vd)n*Vih&Ho0t=D!cF`QHTB{13o2|6Aai|7~#1{|>n3zi~_V_}Bc8fouMo z;F|w&aLxY&xaNNnT=PE#-jRH^!E2JkX>ecM0oQ!afNMVIz%`%q;F`}axaM;KT=TgI zuK8R7*L?QCHJ{7irf#=`Yd%-OHJ@wXn$L6Kn$LCc+D3j~Xn<=z`{0_-O>oWU09^CA z1+Mwr2G@M z*L<#nYd$x?HJ^QO&F3b#=5qk9`P>56d~SnlK6k)1pN+3|kAKbQ7`W!M39k8^05?58 zPMbuR?M4gS7Egg|KHK1$&uMVYX9ryKIRmcwoCDW<&Vy?{yWpD71#nZ|cZ%qe&n0lp zXAfNSxeTuPTmjd7u7Vqfaz59 0>)qo&Dfw)I`;x e6E9QJ~zNMpM7x6=O(!3a{#XS+yd8pZi8z+cfd8D z4ZnMwYd*)oHJ?pz&F46{=5qpE^Ertw`D}q}KBvGnpKWl>=QOzHvjeXAoB`K-&Vg$_ z=fO3fU2x6k0=VXL5nS`R1g`n)fondO!8M;N;F`}>aLwl$xaRX5xaM;mT=TgBuKDbP zYd$x@HJ<};&F40_E&1F5ugLbKaeMbTHzc=X;F`}SxaM;l+>rhg;F`}#aLs26T=O{v zuK8?(Yd)vJJ;`AQT=O{tuKAn;*L=={Yd*W+n$HDrU(OpA!8M;t;F`}KxaM;iT=TgC zuK8RA*L<#lYd+6`Yd+V(HJ=;cn$JGC=5rHV^Em+5d~ShjKDWV*IKK~ez%`$ZZ*-4; z&F2`n=Cg?|`5Xt=d`^ICJ}1F7pDl3B=M=c+vkk8KoCeo?cEB~CGvJ!fIdIMAJh *L<#nYd$x?HJ_W{8Oi4W z+>`ulfw#rm;F`}JaLs4qj_z@;+s`p@&1Vx_^EnQ#`J4dPd`^OEK3m|9>=&fKHJ@#8 z&F3_@=CcE?`J4gQe9nPuKIg$TpIvax=K{Fqa}iwgxdg8H?15`Om%%lkE8v>XRdCJc z8o1{39JuCl9bEId0j~M%gKIuF!A-e7BmmcZZh>n)x4|`^JLr M^VtU1d`^RFK0Dx=&lzyd=N!1^a~@pt*#*~pE`V!3 z7r`~3OW>N%9=PUn8C>(Z0 zw?=rnb@1X=_6B$&$L@nW|H|G3ubj&sfNMUtz%`%S;F`}JaLs3<***R>pJU*f&nCF$ za~xdrIRUQuoCMc=w!k%?Q{bA L#e4zAnJ4RC)uxA(y{pPS&C&jGmRa|>Maxec!Q+yU2oHty~o|C-M+aLs2E zT=O{&uKAn**L+TbYd%}xn$Ib4&1V~2^EnN!`RsseK4-u+pL5`v&v|go=K}bg -2>NrE`w`6SHLx&tKj}yx&Iot=JOnQP1@JNJ@E#(EAE4v@8jt<(IvM7 zaLwlyc%sbxw85=6vv %jZ-Lju zQ{bA xPA}Zlg}rX z!HX4cUjcU{x2xcq&oyvgK0i1IuK8RC*L-e(Yd-tnn$Jye&F27I^SK4C`P>HAeC~j2 zJ{#ZZ9{-xpF>uXi6I}B-4zBr}0M~p@f@?lo;F`}VaLs2MT=O{%uKDbMYd+_|eaYuM zc=1LaS1!0GUO<=6MHay|pG)AH&mOohpSvuBYd%-NP5pc;xNbk!z;*k14qUgN>)^Wm z+yK{n_Q5^*{9qH@5f8vk@fNr**Kf4JHJ>}+j |013PU$mb4&x1c!+yy^Kya0ZvcoF*Ho0kN5FJA5Zpi ztB KE|%bim&%o&o=sj&tz0O8Y$cwc;-LyTl9N?-ehCUoTz)|Dd=B z{t@vq_$S0G;5Uj_!Rz8R@GptafqzxJ4*omw2KXPveeegwo8XH!@bU`4_ZM%0KTf<2 z{xOI9>42}kfZYhX$N$%*o*4KY;wJcA;&Jfrh$q1B6;FcySlj~tnRp7kEpCJVRy+;< z2XP1dFX9>SePn*-z#k)?2R}gE1%JGF0etXo&ch=3q0+tteuTIOezbTQ{GY@t;7<{+ zf+xjm;3tdEfiD)XgRc;8fImmv2Y 0Bk;6vgG@KNz3_?Wl_J|Uh0zewB$-zJ_0zeL;te}#Ak{8I58_~qhx@HdFN z;8%+mz{}!A@N30O;O`dqz^@Z8gMU!G0{&m(Rq#)W*T8QQp9BArcpdx}@dmgr?t|Yc z-UPo}JOKZmcnkc8;%)Gsh 6hu;0K5&!4DC) zz>g45fgdYwgU7!<7+vp|2LG*mzoG;FWa%dZe!O@N{ORI(@RP+|@MYpf@aKw`z}JX- z;Ae=J!5#4m_zT6W;4c=hfscsKfnOkA2fs+X0e-Q#5B@UoCitb|0r(Z-E$}PF+u&vK z4tPb}_(AtLzfL>`{$X(w{1f7F@Xv@Rz`rD(1iw|>0{^CX3jA(y8~k4JH28huIq+YI z=fUq6cftQ4UI2eUya>MECSKo5;0K6%;D?Bp!4DU&fFC1X1%Hxw4g3W0Iq(z3>)_84 zZ-6fo_raed-UMGQ9)PFCTj1-(+u-MjcfemHZv3!&JZ~0{f#=0d@G0>)_^fyW{1Wjb z_$$P1@XN&0;3aVf{Pp4)@HdL*z^@k1gTGna1%I1(0sLC=BKW(+OW^Mj_rR|cFN42d zyaN6q@hbR7#cSZ75T66TQM?X*lXwICi{d_bL%a!ot9Sr@yLb!yPVqMQ-Qpea?}{5g z>K@PcipRizC>{sz$oZE9c+oyI+8#)PJEyW+;O6P PN$=`e9fVZW620U=M zpB#8a>db?;PviDQbU8j#0>9=$&S4My>2h4C4E}a$UjZMG_EqqAOZyu5Nz#4}{Ca6$ z2Y<4(Z-CdNy$^nacoY27;sN;Q#9QEB7Vm)PWjPu@?jDEjMMp;CCk9> d-Mx5S&^ zWtndQxc6@Erv+XSZ-WPia{CUro-a1;>mJW~zBmT1=Zj5nJzpFL*Ym{*a6Mm~1lRM$ z7Py`-PJ!$BVjEn~7pKAXe6a(r=ZiDodcHUZuIG#M;CjB;1=sV%1#mrITm;wi#U*e( zU+jVF`QkFTo-eL|>-pj;xSlVrf$RC=IdDB+TnE?l#SL&hU+jbH`Qj$Ho-Yo-^?Y#) z+?VTi+TeP=xPva|i;Y(I_}A-sV&Hl`j|pCv^()@T6X1HjI0>%ji!E?HUz`Hh^Tjr} zo-a;=`% -pj`xSlVr zfb03 raE5*Rea`b*VE0?!TAY z=fDk_pLuXo>T$ts@d9{F`YD2&;wA8exCd^Dm%-aI-3quX^Ro)>$$Y4R2e +ttQTyT^I3iaiEizlz;N zciH3MMwUGR?tOwi37+uSE%5ef>?v^XW9&A#{~7i)c<~+V4!HAG_6&IY0`?qu?!D}J zaPLFxE_m=Z_5!&5N%kVRkzp@^*I&l&f$R2389X<{?JMAc&t3(u)Yxm_{u|im!0kcy zI=Huny#a2$p4|ucUdrACx6fq{z}@TFTi{Ney$zmtA$teB_yKm~XWir9zLh-&UcZgq z1TTJ+Jr3?(&YnQOjy(xpNwZtvIa$9_;Celq4eo5>e$wE@8`&N3#5>tD;MOJVIq=FB z_B^;T!tR16u3;~LTRHY3c=1i_C2&*n&;!rCj@y^PgK72(xO*jg4cxq%^LY;3{uZ~d zgBQgc;F`}qxaM;c+ m;%@RM;l!CAJgEv|LB10{$mDQ_aAfMy8oC5*ZoHqT=yRf z;DIdPBDl9dFZU9-?mv3qy8l=P*Zs!|xb8nz!FB(!2Cn;$bKtuFSO?er#|F6WKl T{YM*I_aD>Xy8q~a>;7W~T=ySy;JW{q2lw{laqEKHcW_P?z;*wz2(J5& zC2-w;EQ2Q{pDW-wnQj%_6|aGN;&b4d&vkI!er|wkKKtOB&rNX6=Kx&uxdpEI+y>Wt z?tp7P8^7ot=bFzkaLs2ET=O{&uKAn**L+TbYd%}xn$Ib4&1V~2^EnN!`RsseK4-u+ zpL5{4{hSBSeTT=h3$FQG0M~pjf@?mPz%`#eaLwm3xaM;OT=TgKuK8R8*L +!uDxaM;nT=Url*L*I5+mg>Ea7Xgi z122k~!FBt&0 Wt z?tp7P8}06KuK64TFW$xT#{}1Wj)QAHC%`qIli-@q7P#hf3S9Hq2G@K}gKIuJ;N~~^ zb!EUcpL5`v&v|goXBS-axd5*DTm;vAE`e)4d*HeM=K9Ov#c#7$!0kKPtKgc?HE_-6 zIdEULlk4Dh-Jb>5eD=XLpPS&C&jGmRa|>Maxec!Q+yU2oHhu-;U;cZnF>uXi6I}B- z4zBr}0M~p@f@?lo;F`}VaLs2MT=O{%uKDbMYd&YdHJ|g~9XbB&f*Ud3o-csg;ze-x zS3C|&;F`}KxNbj}!8M;N;F`}>aLwl$xaRX5xaM;mT=TgBuKDbPYd$x@HJ<};&F2=l z=5rg|m(M|Wz%`$ZUw4ml&F2`n=CcW|`5Xt=d`^ICJ}1F7pDl3B=M=ap=a+48&F3_@ z=CcE?`J4gQe9nPuKIg$TpIvax=K{Dd^%ucC@e;ahKYQSs&t-7U=L&f7*OK$#p5%57 zT=RJjT=TgOuKC;m*L?QDHJ_W{IjJ)M*L-e)o089MaLwlqxaPBQfA{#;e2#%@KAYg0 z&v9_g=LERsa}r$h*#g&mPJwGa+u)kdIq>#xIEVA#uDA G`J(n%K d?nJ3L(% zye3`%uSowz@S1oDJdl1o@QS?NGPv=3uBQU-Nc$>yU8Y+DugH9!19zmJI{IIDx()C^ z>hZxn89z;MU+M|K4XM8c9!UE(cw4-KF4HxB+dcjbS?^-trnm{75RZdf;t6nDJPDqV z@o9nA#8cp|j(_mFysk93Bhz)jbK)6rSC&@}ymrDdQU2t?-SgRt;Pt1lm%t6_#{>6Q zaQia2Bke2T^>J=r1utH}UITZ>*z4f6qkyaQ}Gr0Nk?JTi}7TZ-biy z+`a?uFJ?D>*FA3A(oYQBcrv#)!A ?QD=OxHtyI=3%_TQZ+3 z;QmT(Uj=uho*H-{%W)3em3r#n#WT472D-HO!9DRNcw5G80A7*yE%2In8@w*w0r$m? z-*=DyKs*NSFX7i^f_t*O;^6kPxP1aVcM5wFye-qUzzxa06uA9V?#BkVq h7 zl2;jUPul0eofJ )d&*%Ot;Kqm8tKg=14Ll(}2X23u`>BJQ z|Ha+_x1}E++!1erd!OKb0`U3=*<0YgxY6kzhwTq=`xv+(?M-k~JPvNxxSs^LCGC^o zj<^Nxil@*&$^F~l?N70%!HpZ)9dP?2>>2RjW9&KbqSTWI_oN>e-1#W?Qvmm6xfj7b z>AwVC5%<7r;$?7Sj;C7z4`lgP!HY7k>fn|4a6b)j>t*adxGmlUcf GO2k;Ffp?+!i jrr$HBcjxqSk>F4Ikd`{EY3c{}%$0ykv3Hn=10)8ILA2iz6U zfEUH{;B#{R+68wTJe~{SMe!oICtd=#Z{~hHa96wxUJ I& -aB&8+Y)0sDSJEuY&9NuYv3Mp99zNUkBIm-vHO~?}NKv mQ#LM8umt;JHJK|OF#Fx2!4cro+1NX%1 z;5G3ExG(O5+h39K44xAYzyt9XxFI>y2DimK;EuTQm+tYL6F0#V*YM{u;^3M~3Gkxy zlLYtg<=1P0Yc8e0&F^!28(ecK4X(N5fNL&gz%`e0;F?Q$aLpwb+>km8;F?QCaLuI> zxaN`vuDMhO*IcTA>-%yQT;G>#;F?Qw;F?QyaLuI#cw3gQ4_=X6YJ%(X4Zt;*THu;X zZE(${4!Gu$@j&-D4`h7Cz%`fR;3*mZ32+_%NpKzi7I;F&NeW!YzYT6l`!u+Ye+OL0 ze+FE~e-2&x&x7mucfoc17r=G=7r}M>m%w%Wd*C|$%iucxE8sf*tKd4{YJGeTT*rSM zT*rR{T*to;UXgLtM3?a&fcxSta2@|`a2@|0a2@}~U%SV-j{g|Aj(-ze$A1Fck@24d z_YZhtbpFl)Z;PkEJ^9?U4X)R%rNQ;OH3wX;Tg!m!b!$0ry>2ZJuGg)(;CkI!0bH+J zD}uXE (=JL^}4k>xL&u`0N3l*d~m&P ztqHEztp(tE-C7G=uUl(_>vd}#aJ_ELFnD_;7L7i`(CgM>;CkJf39i?z#liKuwFJ0c zx0VFg>((rAy>2Z9uGg*E=yKg!8eFeibHMewwG6ob5*~*+aJ_CV53bj(x!`);S^-?I zTPuR=b!#PXy>86|PyFS$Xvvkq^}4kRxL&tb1=s7=YT$a^+8nrEw^j$&>((0Jdfl22 zuGg(K!S%Ye0Nj$-+XC0?*4p5D-C74+uUm`l(>?z4lFuf1AUPZdHy+31BmwTen>`8c z9msBho6?UB9?0uWgSW*UaQiUsKLc()fjtMF`zQ81xcLB&D;NDBZeIXTNc$qVEnWgQ z58-}1@SL UYGkm z3=66#L=Y&77!|Z2LjaWu=ne{45i#Oa6eNgf*)qh9V8ty)=G49EvO?%*Blw2eh8e zl7J|cF$iK&(5Rr*?GW5Yt*F~`o!|Gqy{^8``K|w+*LAONzfC{A)8={FU$6WKALM8F zD37jxIfv1IgyTPf$MOW8$Y=0Yp2DLj?0*g~ ad<$>nd-y2t;L#1jeh% bExfvE*#91$%R6|YK0m+ 42t_45&4DnGz0`3c@?KO?+W{tO?Kk8X(hSAGId zl#k)9@>6&xPvE^gg&*~LW)AoDmJIIeEem+9_v1O-*ISnGQoole;J)6ng8O<)3HSAu zH9Xbt=_ ^FM`q{u8+8e+Kvbr*O}I2A}+7*!KeN`Oo2=|0Ue>U%);8E4b&sgnRziaL<1Q z_xx|*p8p!|`QO4l{|!7-|L@?Q{}%4~-@`rs9o+MOfP4OXxaa=}Zyp$Wdw_fXPjJuw z2>1M-;hz8K#+P&M-#1O*p8pu``Jci){|VglKZASzQ@H1U4*$>jr~gGbzc1j4Jcp<9 z0zPO?R`5|?!XvFy!7JrA@LFEO8+ik7 jjVD<1Y9V9=$$z0*~b>yu04#ME~!9b9gV$;OW1H`~sfI zm+<7L!u=`WmGUe2@M)iWWj`f6y+!Z}KG*sic-Dt}4bSCUc=myiZ{X?sg74sk*6HBc z8$ ?z#DlCZ{<^XCr{v&j?WC< zE1$v#`5ZpVGkB)suz*LO6OMBZZ*LiV2~R#dctO|tD|o7W3D4wfcrLHtg?s~#bh|aY zRDKJOZy)yGz$@i<@Z$4AzJ=Gy@8QF(L%xGITIWF5^S7sK{Udz%+_3%t?{vE-cq1R- zmHZ4Z g^#xi`%mDl?(YoVE1$w!`5a#9@y+1zhr{hI;Enn% zhgW|P@=JL9-rxm%P<{n()rTcKR(=hy)vFae(d};Fsl0|~@-2M${jmQAKK@bg9X$D9 z@D`qH{XM*pckoEZ{{T<4pB`SwkML6K5AaHUf)9FpM|k|9aKFy*{{6wDn_SL+a|}L# z*Sg&p-e~_*c&6i!z T!V5hvb9kq9GI;9whd0`P4)5ekc&7O*;JxxI_#iLg zqkIjIwEqeo%Qx^r`>ElT9^WlI)8p8{TOH3GJkjm8@KnBsSDLpDp1d*4&jFsh|KZJN zhWrs;$p?5LKfyEk2rq9J)<455dGsll^Iyv+@J1fPTlo~;$rE^?<1>Tz%BS!_K8KI; z44&vXEZ~vOJ2^a-FX5fMfJa(?1@DzF;e&h)ALSK1((AeneE5v;IM(o3`7ONu^pJ1h ziSj#mq4{j#sq%Yxr~P#BM7Mi@H+ntO!!xaagqODr`yb$qZubPQ N`PT{>gf# zKjc^NLHQD1Y5g^PRK9{ow+-uW;IX`hC-N;kl{fHAzJurT7GB8r@KWBvEBOIl%X@eu zKf+u20Po}{crPE}gZvC1<EFXVH0DbL`Q zd;zcJIlPfC;jO%Yck&gymzVHCzJ`zT3Lfdawt>g;8lK3v@KoNwGx-jl%UgIM-@`lg zPX~{FHO$WeKKyd<9^U;@@FTqW^56qJdPDFNJpZ=fBRqS3@H0Hw1&?laIsfflgHPby zmjsXD*%t?&!pENtp1|{$1fRjjFASc-le+|;!;3U{22Z!a7w}>hJcrkJ48DXn_Y7XZ z^Xr1I;N6{qm+ aR1(O1NZMeYq)>!xrO`po( ;?+`sqi;r_knk*@s=@KSz)`}dwB+`soc!~J{D=+iFe+`spn(Di%&7+z~XQ@DTc znZW&f&l%jm_e|mbz2_WW>2@=CBhTT5=6?wvo*3q{fTv~f75!nK7k&=~FP U?a|f^GEj*R);j#PxKWjfdyw!e=@J#Cr@c1rapC@>( z{0NWa(akUCGyZA)3?H?h7+z?dDLmJH5_lz_!3X&qKGS|O_@MnP;HCDH!_&Kl zt<%E``4L{q2Y4kv!E5;lZ{%lqE01n*Ik%mB0`KK9e2`D!sg6Se&*U@ssC80!r2HJ7 z$TN5@U%+E|4lm?OcquR7jeG@<-yG(lglF ;dZz1R{Lz=gS>@T zn*TlAue&<9Uw0kge%;l>{krQ2Z~rFje}MaS*9q>|T_fDDyUy_HU19y`(=X?|cx&(p z+^@T0c&ER&oWlLOD}noU*9`90T`AnJyXJ7e?#kf)d$R@H-*1q^{km%j_v@|#?$=!_ zxL #iE^*Iiq >Y;F&yykM9WU&*6 #q%dhL>L-Ji7Je{HMxK;JG}8 z_YV&1Oz95^p1{Xf1fRhx )w_CwSJgkNe=!Z7%25-;Xqb`}>h%xW6B13itOTCGfltw>yLT`;k(( zzaME1_xB@ZaDPA20`Bie%HjTgq$S+nk5s_@{YWdgzaOcDm$wf0cMXrG!7F$r-@yI- zNHyHwkF w4{r?s6N4UQqX@L9t zkxp=bKhg;I_amL*{(hwBGcM=f-;Xqb``=xS;r@Q4Dcs+Wl)(M{NHe& FI)ll_am*~{(htq?(avc;N?rgao@o6+lG0l;iY^F zcRx39_wx?!es1CJ=RMs0+`-+?2e|vWhr6GT@cNZExpF=l;IaG!cR!DC_wyO E0X zw}R)d3y)U`cR#P;?&k{be%`>{&o$iryoJ~LcUK#_zQ1k<_xIPeaQE{b-uvIhhP$5+ zaQAZ$cRwHD?&ksSem=q7&m-LZe1^N9quXNs{qJJK-On-H{XB)cpA)$Ic?Nesr*QZ4 z9PWP3;O^%I-2I%x-Oo$7`*{W5s-H`Ef4lHJTEk<#uCCzj=MCKbT*Gs{Ufsgo&kfxD zyo0-+Te$mq4|hLzaQE{8?tbp!?&l-i{XD?k&nLM1d4#*4&+z;+!s8Ns*5#bLpC@ql za}0MsPvP$81nz#G!QIa(-2FU<_s FfV-bpaQAZwcR#P; z?&k{be%{di?_$H<&s(_rxq-W%cX0P}3wJ;7;qK=S?tVVN-OoMT{d|PGp9i@6`2>&s z?_$H<&u6&%IlA5D{JWngaQAZzcRx?z?&k#VexAYI&nevfJcqlVGr0RXhj;4dCA|2o z@H{H$v*0VZ`?-X>pVx4|ey-r|=MCKbT*KYZTe$nVfxDk~@HPwgtA%%W2)>8ApF6nw z`2cr6_i*>~5uR&51H9Jj>J!}kJi^`2XSn-0`s~X&cRx?y?&lcpexAbJ&j~!%>&zLv zSC6G|_wyW{>-BR6cRw%S?&loteqO@e&jq~uO3gpq{an&@zt-?d=a&lZe%`>{&ow;J zoNVFl=LYV6-of3^E!_RQhr6FUc>I~+_#EJa_SwVT&quiXd4Rj0PjL712zNi9;qK=s zxtxFZ^91gGj^Xa-Dct>>z}?R?xchkyKdGNHc&mP1zz2B_cRw%T?&kvTeqO=d&n4Xb zyoS4 =_Ju0~Sc>{Mp*Kqgq7Vds-;O^%g+^?To_;78w z9<_(NpF6nw`2cr6_i*>~5$=8-;O^%W-2FVl-Op#Z`#JjD%lUUdPvGw7818;f;PKCd zb!YHQ*O^mzC7;9H&l%kPynwr(bGZ9?33opiaQE{H?tU)e?&me!{anFg_4Wqtey-u} z=Plg*+`!$>J9zSl@O*6H?&m$+{oKLb&j+~sxre)-k8t<%0Czv1;O^%U?tVVQ-OthI zUCz1tc>;Go$8h)a6z+ab;O^%c-2I%w-OqEl`#GblhZk`7a}IYuFX6p Xh0 FfV-bpaQAZwcR#P;?&k{be%`>{ z&ow-D&%@o%4cz^_gS($wxchkzcRzP<_wxbne(vG!=Of(xJiy)0C%F50gu9>5aQAcc z1();hexAVNM}_lX46oJCQ@Hy%fxDk)aQAZxcR$bJ?&l2deqO-c&pF)vyo9@-3%L7v z1$RG}aQE{X?tZS|?&l5M{anM{&s(_rxq-W%cX0P}3wJ;7;qK=S?tVVN-Ooq(M*Td% zWA*R}-pWU~`}quaKS#66Id?x#;O^%b?tY%a-OmZ!{XB!apHsN|c@B3!XK?rP0`7j! z;qK=p-2Gg@-Onqy`?-X>pVx5ra|L%lZ{Y6d8t#7H!rjje-2J?RyPsRQ`*{y{KX-8V z^8xOD?&0p|Bi#Kwz}?R$c>U owoy;CysGmEy`}qKOKlkuN z>mT9n=K=11KEd73Bi#LbhP$7mJ7doEd%y|Y{T##H&r`VjIf1*MXK?p(3h#AYVh(pd zXK?rP0`7j!;qK=p-2Gg@-Onqy`?-X>pVx5ra|L%lZ{Y6d8t#7H!rjje-2J?Rr<%hS z?tb3G-OnA|{d}OShkLmD`3QGE4{-PM3GRL#;qK=%-2EKg<#PVr&l9-&IflERr*QXk z0(U>p;O^%X?tY%b-Om}^{k(v?pL4kTc?owv7jXCU3hsU`;qK=(-2Gg^-On|AR6lRw zgXXq@_v+^z-2L3b-OqctUq5$n_wxbne(vG!=Of(xJiy)0CwQ%X9^vliGu-_g-Su+L zo1f78R=E2)hP$7qaQAZpcR$bI?&lQlexAeK&l%kPynwr(bGZ9?33opiaQE{H?tU)e z?&me!{anG_&l|Y=xrWF3JL)am{oKIa&pWvLxuvV0_i*=f2X{Xo;O^%h?tVVP-OmHu z{d|JEpGUa+`3!eIM|Z>g>-SI-xcfPVyPv0U_j3YwKhNOq=M?UKp2OYG8QlH6fV-b_ zxchktcRv?!_wx$weqO_;FAc}Hg7@m@4SY~P*Kqgq7Vds-;O^%g-2L3b-Oqct`?-U= zpAT^Na}RevAK~uj0q%Z2!RuFqzw;d7?&mYy{T!v2bMAhgz^liF{*U4A=PBI%oWR}B zGr0RXg}a~UaQAZtcRw%S?&loteqO@e&jsB5yn?%*OSt=a4R=3RaQE{D?tZS}?&mFC z{oKIa&pWvLxrMu*_i*=f2X{Xo;O^%h?tVVP-OmHu{d}UUH%7So`3x@~6XrR(`{n$* zpC@qla}0MsPvP$81nz#G!QIa(-2FU g^A_%YZs6|c9o+rg!rjk%c=mZ=PCB^z`2cr6_i*>~5$=8-;O^%W z-2FVlYhCv^!`;u(Juc_m{XBuYpJTZDc?x$wCvf-k4DNnT;qK=--2I%v-OmfS`#FcZ zpO 31= z!QIaXxcj+>`})Qa?tUKNu|D5@g1etbxcm7GcRxq>yqtgc^91gGj^Xa-Dct>>z}?R? zxcfPUyPxN9_wxe2Qa|VL D|oNZr)=Qv=Nj&Q-oo9_4cz^_gS($wxchkzcRzP<_wxbne(vG!=Of(xJiy)0 zC%F50gu9>5aQAcc#h3H%exAVH&oSKnJcYZT6S(_1h1crmIlR;Rlng%V`o;q8e$L_U z=Ox_zT)^GWE4cf)gu9>DaQAZscRz37?&lire%`{}&ka0%O6Z>*e7rVz3wJ;7;qK=S z?tVVN-OoMT{d|PGp9i@6`2=@Ak8t<%8SZ|LzT|SwWA)nv?tYHp?&m4o{hYwv&oj9D zIfc8Q=WzFP26sO%;O^&~t{z^(-OmNw{k(#^pG&y=c@1|zS8(_92JU{Y;qK=x-2L3Z z-OoF?`?-a?pZ9S0a|d@nAK>oi9`1fV!rjjU-2HrlyPrq6`}quaKS%evoPYQ81nz#G z!uRUu1m3EjXYfehzmdY-&vUr@IfJ{O7jXA;4tGB<;qK=G?tWgu-OnZ5{k(>|pDVcg zc>`~r9(uTj=ejPvg}a{{xchkrcR#oA^asN2?&0p|4(@(Fz}?S1-2HrnyPpTR`}qWS zKaX(t^BL}bj^>wh?tY%Y-On-H{XB)cpA)$Ic?Nesr*QZ4oUVS(;O^%I-2I%x-Oo$7 z`?-L-pI31Aa|w4pui@_J3hsX1z}?R^-2J?TyPq4l`*{a R_wxvMKSy7BIp>4=c>-_L&oMk!KTqNA=LGJ4p26MEDct=$hr6FM zxchklcR%NF_wy3&elFnd=M~)jT*7Ph@EY!ZuHf$H4cz@)!`;tYc&_Wx4ZL}cUeCkb z&n>)orFtIje(vDz=L6jR+{4|^M|jbM+a2KU=M&uhJi^`2XSn-0y7%S$yPqd;_j3$) zKTqNA=Y+0)p26MEDct=$hr6FMxchklcR%NF_wy3&elFnd=M~)jT*BSYYq F6d!`;tIxcj+)yPsEZ z_j3uae>n8jnywzM;O^%QJXXGjyPvo4`sLy8Pa3%Uc?Wktw{Z9K9`1hb;Jte60Czw4 zaQE{O?tUKN?&lNS{XD|m&u4g1g~uhjFXrFZZRomRG2Hz;g}a{ FfV-bpaQAZwcR#P;?&k{be%`>{&o$iryoI};8@T&<2X{ZW zaQE{b?tbpz?<!UK{4@2(R_}d4MN+{d|JEpGUa+`3!eIM_+b1=kDhT-2EKG-Op3F z`#FKTpJ#CQa|(Aq&*ARp4DNnjz}?R|-2J?S_v+OGUdvZ-_j3s!pP` g^A_%YZs6|c9o+rg!rjk%xcj+-yPpqm_j3<-KOf=l=K ?&lcpexAbJ&k5Z9JcGNRQ@Hzi4tGCiaQE{9 z?taeU?&l>ucF)7z&nvk5xrDo)*Kqf91$RGh;O^%d?tb3F-Oml&{k(&_pIf;5c@K9# zcX0Rf0q%b8;qK=nUHv@3-Oneu`+0=BpU-gjbCg}qzx#OtcR$B)_wy9)eoo--=Na7n zoWkADbGZ9CgS(#>aQAZ#cRw%T?&kvTeqO=d&n4XbyoS4 w zc&UDF;hFk*4|hLzaQE{8?tbp!?&l-i{XD?k&nI~9&jZ5U&u6&%IlABFoV%YVaQAZz zcRx?z?&k#VexAYI&nevfJcqlVGkEf>aGqMg-OoAP{k(*`p9{GAc?EYrmvHy<8t#6s z;O^%Q-2Gg`-OpRN`?-O;pLcNga|?Gr@8Ry}j_&I=aQAZ$cRwHD?&ksSem=q7&m-LZ ze1^N9qp!G}fA{kQ?tYHp?&m4o{hYwv&oj9DIfc8Q=WzFP26sO%;O^%f?tWgv-OmNw z{k(#^pG&y=c@1|zZ{R!ia}6)l&s%t|es19I=N;Vr+``?@d${|#gS(#(aQAZ$cRwHD z?&ksSem=q7&m-LZe1^N9qp!T2bNBNE?tYHp?Q=q}PT}t71nz#G!DGFCPT}t7Io$o6 z!QIabxcfPWyPubE_j3VvKd<2K=MwIIUc=qb72N&2fxDk;xchkvcRx39_w$afes1CJ z=RMs0+`-+?2e|vWhr6GTaQE{7cR!!t?&lHiem=w9&(Zxa=imK2fxDk$xchkucRwd^ z_wx+yeoo=;=Q-T{oWb4C3%L6^hr6GbaQAZocR!c#PW`-wmxs@|^6yz!@Jhacch`Ku zm3$4)emAVMg_rUM9^ELcvxE1_xA5p4Vf{V4(K;P`&^{0FTI=-iMC%;kqw)iMcxTwp z2_C;I_y|vwKf`l*^nlAbZ{-tsruAcZrTi3LDxbhxJ-#z|uj7`&8?7^k_wo!L>v%5U z>AS=6$>D{339sb^e9-<^@L2P+hUYH}$G3u4ubo|)=MB7;*YLOt`7OLrzJa&$7QWJc z_HaKRJGh^Z2Y9XH(8K+FJkmFJ4F7&TywPzx!To$3;eI}z;i;Z4(E~5%xp{23-3i>! z#~9veohjVU#{}-@;|%WSV+!~4aSr$MF@yX0xPbfln8W>iT*Cc)EZ}}VuHcOx-;%E9 z-5OrMFdXL!9_e|sf&2Mb!~J~R!u@<~;C?>t;C?>t;b+Z%2X9{<_J4q _X|h3 z-!BYszh5}P{eEGD`~AWh?)MAPS6$Az-!Dwye!mdI{eEEz_xpte?)M8bc>daOd{Vmp z&TtO*`-KeN>vjGD?)M8h-0v5baKB$D;QdR&K38zRUnt>zzp#e;{Xzxz`-KhM?-y#g z-!E+8e!tMb{eEEw_xptw?)M9OxZf{yaKB$Tz(*ao9`5%GN4l@K!@Fz4@jt=+eqn_B z{lXdU_Y2YDa{m2(VFLI2g&6Ml3sbn?FC=ikUzouM9iNo0e{XRP_xptm?)M7|xZf}2 zaKB$z!u@`sfcyQz3hws{CEV{9)^NXHsNjCTuz~yiLJjx(g$7< eeeO^e)<<)+5ZWizcl1Wc=5d8XL$7Y!K1If zobyrppTN^0 bFNKeP8}f5_B+ua4f4@tZPk4Ua?XP$a?|(n!m+(^g0$#~i@LFEN8~GZZ{ad)* z3f?KdfwzAhjzbL}l;6VB-wgM+foJj^eAGHEJW_rSPvjlk`#iv7<$HJ`Kf+7-0MGs* z9ETG;esj3LBRrFz;iWwKn#(zF )Z z^c#Z5@Zr|Mr|^0mJb@=#X9h2{{}f*SLRe=Gk9EH?c>9EqU%*q{ublopA-{yjPYzzd zv)>E8f)8&FUc!rS3ciMq&ktU~YaNFTe0)R5*YNDs!ME`88-q9SQS0yE!`Fv=3-9%K z?cvqiL%xIen$H9MpTd0h@c5U)I!Ac@E5Qf&@aW(tc=s*AM|gHk@H4!8Wbo)~FXunj z{hh!g9k&>sYd)v&QJ%n~Cx+Xd!8^@o3eUeg
#d*6`?I!MF5x1#jS;=5q&cG@mUz z`{l6C9$sobJNT&iJiv=(Sf_`#*AIS#Cyxj|z$4A)30`XdBRtjPa)$Tv=pmSY&F2K( zYd&Lm_Smq`DLjdSC-7ABIfIv)&lKK%TUcifPc)wyyw`j#;EC>U4v#dSOL(REEa0i; za|O>dpC!D}e6H#52={9PpXz-_4ewtRUMFnfbsfB+>wCp_a9>Yu;l7@{hx>YR2lw^l z1Kih>d$_MBAK|{9JiwD(xL+su@Y>)b+}D%O@LJc6qp!Q1^Hl3h;J%(5!+kw@3itKo z1n%p}Gq|rOr|@2%bDYC{JvoE>dh!D9>&ZFX*OQlUUr#RJzMi~-`+9N-_x0p8+}D#U zxUVN~;J%(*!+kw@3-9$gng;Ic$ve9L{si8?FwE5+?(4}NJbF#YAK=}qg7@(1mBEj2 zUr!$3zMg!7`+D*S_x0p6e9--h^2_ >)f%|%L4EOcqDZFaJItkp@lV@;WPfp>! zo;-*9dU6K$_2dQI*OPO&uO~0zzMfpbeLcB^r|Rc5yih+^@KU~k*XrjQ?tb3FM?H=W z-2J?RyPsRQ`*{y{KX-8V^8sG#@4b6?EI-2C&jUQt@j1bB_4WvNKcC@=)``CUa?ah) z6S(_1hP$7qaQAZpcR$bI?&lQlexAeK&l%kPynwr(bGZ9?33opiaQE{H?tU)em5%co z?tZS|?&l5M{anLit-pom>g@*Ze%`@Tt<%CY`5xZCDtymC2X{Xo;O^%h?tVVP-OmGE z`#He}{l0I6yPwZ+_jB~n%lR+$eb*DX`#FZYpQmv5a{_li&*1Lo6z+bW!`;sr-2J?O zyPucvrTV#mC%+!fQ!98OFX8>0Lw*f!el2(fPvjeTqxEZe_r|c!7M@)fyn#n=3ciC^ zzZtxR=lkG$c=H> eojrdRIQ6h6okxX;ToxX;Te-0z#_aG#emxX;TAxX;Tu+~?&b+~?&29_xOs{y!f2 zt%Uo$yoT30|5k9Hmp5>qmuq |f!hK$D;65+!;65+6aG#g=aG#euxX;T6xX;Ty z+~?&Z+~?&1?(^~q?(^~p_j&mY_jx(`#>+YPd3gel_3yI9aG#f_^f!jbC4r}!pBcPq zLq3K3ygY~dyqv*%eQ)^!Udwa1&&x}=&&vhe=j9dL=j9Ua^YR+r==FI8_j!2(_j$R7 z`@Fn``@G!1eO}(deO_+iJ}>X#J}-B0pO+7CpO<^M&&x-+&&vbc=j9XqHDP|DZ@Qf4 z_-Vl>@aB2JV|el0;8S?^v%wR1^pfB+c=6QWDZKxY;B$B+&*1GVLVf`s 3O>NI9|(Sems)3p5AriS)&8SzzMTK; z`@{MZc)boD!*lI(3eWU-CGbZ3pTXl7g!NN+`@-OJc>c2B8GP6VU%=C+2hZW P5kK@a*})TlmxT z{My4KJ%11Ijb8WmaDV>d2>0hN2Dm?eaf18v7bCpX=Pb@}fBqu+mdiQ!=PxF3fBqtd z`|}r5c%jczByfNJVg~o;FH*Qae=q^A{Q1pTAhZ{rQU=?$2K=;r{$Z0r%%GR&am* zqJ;bN7i;)<_q$)|=L+u6Uu@|5{6!7# 7G`HLOgpTB6~{`|!r?$2Lz@bNa` ze)aH1^M8bU{s*|{{{;8^k8scb8SeRymX~wx`Jcc&|1sS2KZSe#6S(Jp2KW4@aL@l7 z?)lH)p8o~h^Pj^#|4X>%zkqxGS8&gN3HSW3;hz5rp4~0X$p-HEuj!t1xaYrtd;WKD z&wmT|{O{qO{|@f?Kfvo-hWmAdA2k01ylw7zr3X%M&;JPb{GZ{T|L9?tbME<{z&-yl z-19$$d;Sx+=YIzG{HJiw{~Vq^Hynoy?)hK9J^wk}^S^|9{tLM0e+BpamvGPj8t(b8 z;GX{tym(=_zct+Rzol#b8@T6x2lxE9aL@l9?)mTFp8o^f^WVd(I^3@TK4|_=aQFWR z_xzvXp8x1uFX!CzKY@GxW4Pyk3itdcaL@k??)gvQp8q-A^Pj;p&Hn=K`Oo2=|0Ue> zU%);8E4b&sghww9kK-Di$}70%e*^dY*YH-KGu^^H{|#O9zk_@JTe# Gd z3itfa;hz5t?)hK9%iD(Iki$LyOStF1fP4N|aL<1U_x!Kn?*9tz{@=ho|25q6zlC>6 zxW5hD^S`5O{#&@`e-HQkcW}@D0q*(l;hz5^-19%cD?N@QJk|W4;qL$F;g@sn`Jcek zXWZ+`^CgCR{-2)_zKOC9mjD5BuD}Yk33DzjS`3A9nCq-og|49-hiOcqSj<$?d}Jp6Is^KGLrV zeul@_4<3E{<(#B944%O2e+|cH2JikocnUA0u+AJ_|69ms@Ivbs@I=0X51$C@l<-l$ zhDTbbg2(b2p1mR*mo41qkp}Mb$PV6r>b 4=aC-n^T-k2 z#bNycKHNO`3GVaA2={s94EK2?dgSF?`8+a#`#ciEeIA*@eI7~RK99`cK98hupGW3! zpGPvd&m#-?sK+;lH#(0j;lrne<6OXf9$CSC9x35IkF4Q7k5q7 cSuN4D^#=D&e^ z{TCe+y4EZ+m!rQ_VR%k{{rSyoY=Kk8scb08jP$=LGMx{s{N{pW&YWsJNVS&;JDO z`H$hA|0&$_pTIr;GkB`~r|?WZhkO1rxaWTX_x$JZT 9p8pE&`QN}j|25qGzlD4LckuHo!sFe-#}|a&-oqn#2hScC@&o)E z{{4YhUKgF<-zFd7kCmU{PnAdCaXFvQmrvkZc?|zK`4oPgJc0kNd g%9#Q{NwTt9{oo+{s;Ko^>_{Nr`=2Q3{U M{~>Q{f&mH9uz$NwGj(W5Wt z`DgAQ@)LNcd<_4I_dMXr`crtJd;(8?FXU(N{vX4BQuwdEH{|E=;uP{3{1w{&0^a>Y z$mj4+`N?oRm+Ws{412-!RzaX^;`JEmEXh1 z>xFy=f4uSsc%gg`pD2HXm!A~YAK KQ+TR;0>4)I89e`zu%8U> z ;`F6ZPMd|rkZ%E$1f@>6)Md; zUkLZNg0Ga{z;oqm_!E@h!XxDy_>+|1!Rwz7`)T1np!^=5zCPqT_|ug?zzeP4!=J7E z5njC^tUthiMEMiE-G%%Je}VF6c&dE#*vt9|z8+CkE@LKs2{)_6XH9S|o zf`3%|*}xO!YxpCzpDlcRZMeS;{HJt$cJT5QA>YD(O6%|8qaLpgUOg}L^8r4*Jgnct z|MXGe@jAjgtsni@%lW)+8S)c&|Mswc48QU9Lw*V$l~3TmrS)g #;_wXP7^|1dVy!u$k5Ab(tKPPyv<1@lP zsQek8el)Bfeb425{-N>{`1q$GAH#pt{Rz+iJmeGjTK9JbZ~iRgQ}}l%KZoZ-$Y=29 zzBfF+3wZG pXvTC;r)$5zJNbV&-WF4xN*o=@TchbY~axsX`bQVQiuI-;oUt# zzJWhd`5k<`d&sx&hbq5^M|TbR4t~vdg!w$c6RqFFKdS5YM|gR!u>Jslt*)D$;K@AX zNBAdeKWBJ;zmSg}cRA0VhY5VRTgb<7KfY7=aHo(@;P22p%;25&pThk *Ra*zuzy+Z3q7>t$%>OP3!dVcgm0Oe}8-UeZc^Krt&BFkH|;(i{xkcPs*dm zU(WOU |zh3^Wzw}L-Z@Apgi zpS&(SzH9iK9uvHR-$OmKfv1lS`5OL7y4@}OVYdkR2JZdr;Lp(S8C&>m_hFq5{?sA( z0UkXlJimJQS8D%9c=fd*KfwKZ>;#X#I^;*VU(cN3(St)idcx&=ey#R1fp-rK`2_xR zMerH?0gnov!vE#r!RPQd%QN_kzb)h!@Q28A_`Bsx`1gEkSf_yBOt-s&|FiNXeDf_~ zoi+R+@(O-4t+RpuwemImvELll-@+gBu;2~+CClJD`2YT<;4S=H o1OZZ!!9K3)( zLcW5Jy4@1~;O`IXtl@8ZV(@hjn`R7s!wBN{`n7fA! V@8R$Lt>7K}n}0F*0sg2qcn|-bw*^1KAFO#8;6J|)`4jvm%8&4$(LT@c2g#%F zznssPzBQ~rfq&>N!DIOUQGN=)uRMW2RqM>)H ?t_wXlZ{Uf}7dst_HKS;NGf Fv64H z4C|cXAHFVlw7#7Guld#B6Zls77=B;*6n_6Vg>@465Bx^(8T> ;E?*3aNy z^FPD-3;6xCP7eQGpU2^+Uk~dP@E6Ee@DKc2$d~X>lds_qd1J^|@EhoH+`!+ceyib+ zmT%!d_se1Z2L53A4t_oLMhm~Qp1-rFUCznkRpI9pp1y2y<#o{ I2yf*By#Kv${7+Z$5k4q?zKTarznmZEC-CU~;dW#AsP(5;@#HE#yNb`@ zg^qIu_i %{O#>rCO9)=A)%)=A;* zFNOO%hbLMmgO^$-hZkCB36Hc+0WY*p36HhT8a^mr!E<>H?{vFcy7CP?leh5otHa~5 zhx>Wj!Tmfvz@z(o#g+c=;feeRA8!}(C-_eL8R6c~8Sec=KYTe?!|lHE%6=yB=(B^z z@ai*yPvQMEcmhx4DLmCWb9k$K29M=Ae7H@x-6gzMzJN#a5?*VaHM~^5f{(Wjw_C$A zt+R#aTBm^zTBn7N%J1Q+yo2{zr-#p98y@c?+;cL(Jtrr46W#yHoQ&{Jeuh`_=oy#u zR?8<>@fhxP5_o#^u%8(`mZ$JYp23?tgmo71N}j_@c>&M9FZ{lB1+V{G@De_JZ}18} zJ}LMHUTU2h-fNu(-e{d2Jl8rcywf@zJl8r0c&c@Jc&l{=c%*es@L21N@J8!I&%B(U zR{05hR6d5+@`V0#Vci+r&x;i9=fxa8?84)f!K2p)&*A;6gD>ITtAZEsTwcN}t+R$V z%2)7AUc*zZvxQg6H}F*6!Uy>tUdTImBJbhRTf=b};EDVMKj`N(JXJn=*5&*hm5brI z@>BSz{0yG_PS|G(cRqt>$}iyKE5rVl@J{&xKG)ABJkj4Xtl?gN1NS;LJf42im7d?y z?;gB?m$wSu!Yg@C*FKMM@ACxjlpo>FN6)@|e49gfTqbb8&zizp %uxc-*I|NI5v@0XVF`aR+AR13KO?q~(CZm#Q%@aO7zv4(g0oK6M*&%bwq zr`He1r-naY>u=%X{lfkm_)V`1$8!gdzb@>*h5xwL-^26&6F%Q}fG6({&+8uU=iLA= zlt00pKf@d4qvvAYbiRn;gYr|j*Pp?Y59s^_cRqs`$}ix~FX4^y1>E@(J}AG2JHLTv zzaREr!<}#7mGV2d^Lu!!d Fhxf`K;m)7v%8ztCzR~9LagRS3?(YQd^{4Pm`2_BK z3a^x(!<}EiTjg`O^98(Deg$`aO;^66>-g00_z%MI*}}d44xTFC!kzEnx$+0N^GA58 z`~Y`;gg45c;m%KfPc 2|PZA<1>RhKZj?^XK?3pc%}Rj?)(bgDPO{!ujtBe z=$eNuJo!+#zYW~$x9~#wJ>2;NyivY~J3qh& E@(o+-bE zJHLS!%GYq`8+fJs4(|LO-YDO}o$ukD@<+JyC-|WJ2zNeu{^jEyeIy+J3EcT9JW)P@ zFEnQ )<+pI>ckn^^7VdlpkN!9u{{!6l zBRo-lfIC0JGv&{4=O-_?oQFdB818%muauv`ou9)S t6{seda3~!W=UU>PqPxSna;jQvh_^5Sf z@Lu^8?tDg9enHpzOL#nl$E$#Q{SuxkzlJ-%f#=HCaOWF%sr(M^{@lZB O0;m#NELHQNj`87OxaJat}-1!=wDZhm~zk`>`w{Yh>y7C9Q9={{JQ+|MZ z{Slr$Jlx;t$1WfDN 8;w{gm)ZzJ@RL^9CM0E38w)op0cU z@;kVX+n%m`2Ty(~9LFBs>gOXodd;n_{C;YH7xEK4y~XEV$)Dk)_7na1<>MItZ1_2b zkNW?o@T3d-N#L1$2JhYz@+rKR&*AY~LOz4{@&&wm)}5~0-yGijVeln9ds|q)fXCW@ zNq=^jlQmt(Z3AEF=NexBUAWyXyp=a_AGaOc$E}5De;?N0!+WjY!F}8ga38lGK4|?T zy#9E&-4ne3(Qw>GxSuc4i!bMJ)bDF1aObD+*o_ZQGSFU^z-Z|E+5C{ zr^5Ov-1!VXD8GO^zl3)`9o8@4eq2iUp!^2D{F$)M7GBC5xO=;W*UInV&L7~d@;%%= zJivS9PjDZfGkjD&`pL`3#rYT>zcd_&Dct!PywdHZ@c3i5yVApRcp}f>?zaWp{kDeJ zy4?z%{(ZRJ4ZIkG*KnU78~9G^@8Gr8Z{eeS5BK^9c&GJyxR2Wr?&CJVecVoPAGZ;n z{Y5wqXLu)%^m-(Gnk0&RUYNjrUWnoSUxoFj@a!*x&*1Ivg?Y>1gM0}uKN#{Wcq1?2 zxqhzTo$?#F=X?tvl;6RdKM4ET!;@q113dar@FTpEpWwwGhJ5tXm(Sl$K7}WL6!Iy& zk}u$cyntsP4(pWgM!tqG_45YaDPO~VoE!L{{0_d-&wF_Ek+9DW?)7_kqWlr=zCFP+ z x4y%bK8)dhUQgkEUMFxruV?V#R$=`V9^EYX9Pa0J2KV!NLBDfY zCx^#czknZf99Hl~$DxG#IIQ754i((TVFS-^8TMJjhg$^S!hIYXxR1jQK7LMEr-dgv z4jsJE{2bt(pCf#)p9gsQfN;AfxR2Wi_i;PJecYm#T+VHL|FHf9-pFIPkJ}XP @X-seB6e`U|@9OS |pLTGcPY>`x>-6yQvEg=) za3B8x?&E)gmroAsjPU4r!6RLd^!vC<$87@l^I{6`ZxSAt1pc4z_u)SNbGqN}!#(E< zxQ}xV_i S;JKO=YwKYdv^o^!Y##|1q4@{rHro|6KeD8GU`zlLYZS8(TR_)I@< z;qj~HS02X(?#FQl_v6^Y{W$LFuMF#V@J@c9>-U~L+>hfC-tNNs1N_?8UUTJsjqo-L zk4vP#H}~s`UOs`J^#4!c@z;cPGPpl4wt!dq_ib~yKQFd~`}1N2+@BX)!Tou$67J87 zt>ON>SOxdz#Wrw%UaW@u^I}`LKQGq6{rRsQ+@Jqy;r{&B9`4V7b#Q j3xXzk0a8 zANvSTZyG+2c7|tf43GQdRhPXqDwo6aw}$)@?$0l;;KMsZzJxnp!AIpcaDUIq79PJV ztlz->dFK|MD!+&O`zQ|Zh4MYz>ksfk`4e3~pW$o$9KHJT`1|wz6SzO`AH#cnU)B^p z$`iW2za@q5w4XWL`^n(m&jLQ^`<-%lr2Q=6{yvQY?(gMTUByed*QwzBd&8V;;C>vp z@KXOCQv*Nh__Xj!`90k0AK+W%d-z&E5Aas`6WsYT{GfdFn#;LzK8APt|EF+2?g`wF z`wU*``*~7$BcH?lJwglkQTxf^-p>;5{S@$Cx4VLm@{+FSMMc+sHgNB!hI>C-c&giN z;JJJU_wm`oYwf3ldp`%b_tV2`eb3Sn-pU8KKQDi}ijQ!AUjBR)k6wE@pUzL<{=9q) z_vhTFSMlU3KD&yi@Lb=2mBAzZdlL(|&udF~uY3Xb^Ra~I`gsla<6FV~_-^3IEyDK* z)$mNdg?ru_y1pN22am27)^Fjld=F3L2YC6Xp`UxWdtiWXlt00JJkRi>evV#uId49m z6S$9O3?Du}+}|lY`hwsI+{ZJ8ciPV!?)_wN?`Hw8wVxc`%9n8Or+}B5ht*ZQgjdS1 z;kCSiH}Va Z4J`{xYg<}a z*@w X5X$) z^Zh>0^ZWf-SJyT3ey#U?ufwcaGt8d7^?paC!r`UjuX4EOuW@*l_-h^R%`4;Z8u2$f z-1CRZ_MC6c;*WK>=Z|-Ix#Uf7xHnId9PZ7NWQUjLjOM=|Qyg9;Jk{aeIv)<;X%6?+ z?eqYi5x_GY?yd7#4)@mioB*C1!1DumiNmX9oJ$? Tvgc=0ql&J_+X7hdIX z?|iIrc)hfz*5O`z>K*R2$GBYTZFYEu@K%R=?TLMP&w1M_?TK@^*PeKXd+kYZc(cs6 zB!|aIdy*aQwI|i#CDNX-!@c&TIoxYcy2I0?JsA$q5}xUB@4Uzg;5iQW&Wqdto*%%A z9qyeMB@Xw_i_!pI7Qo8`c!k5OmfP!EwZn^lmg}{{y>+bK;Z>rI!@c9x>~QaRwK_Z_ z&z@hQSN5Erxx!-|?#-V#hlg&rdEy;jFY_?L;myL60(gqUV}G&Zlj?A9e9|1AApUfR zd;Uy^r;0zz;bGCa4$lyOzQa9#iNkZnU+Qr0@7Z#PdwD7x?j7GMhkM7j+TqPF+2c~< z@VGL|YaQ+#U*qr$(ajF`+SBTAuRWnx_ncS7Qg5uo%Z0}|+-pyQ!*is*B!_$LNp`r` zo)ni$y{Qh*5FU28*Pe8TCrf)WTrR&$pXqR~Jy{MfmE)D;@G9ZC4)@xV@9=Ute~TSn zCA=hnmpVN8UOP|99G)e-+~EoLS$~DYYoy+)0AB6zdhypdyjggy!@d62J3LL=X&mn5 zZ+3V{{PAz>Id4 -uH9(slu}yo+a=9 yu&MnCpi2g;Ykj!6`t(yEy7bA z-YPuR;jz2zaS1y-UU-_r6NRTce1Px_ho34u)8XTVXE{7Wc#gww6rStw+lA*lyhwPl z!=Dvi;_wB+OC4S*yv*S>!pj|AFTBFx+l5y-Jk(~#zuMu0 @Z1Az{v3y=_O?9N;iba!9UeN&`imW&HQ4eJhu0rx zd8xxg(w;JhCns2cxx-5(Pldya#b4#{X5rNiPwQjz*EqaNc&)<|4!8b#ho?$=jKj+Z zS%0&`GlaJ~ye8TDLreCYx2eKo9bSH{^~X89d7$O-4lfm+;P6!8Ne<5#VDlt9JWhCu z!^@Ac{#1vT%6S)dc)jp6hlhIDJn0V4lKdGiKhXL!9Ukgud6vWDBu|dR%a6AHe3wi9 zVu$C-d?;~vviM6Kp0>ZuU*_=8ewLRzJVDx1;qV%%x60uON7y{o4o^GE@*0O{iNDt2 z&Hb&v-r=D{%Z Kx1mVdJPmQyAQXF1&q~)m&&k!DVc)8?Bb9hMn=?+imZu4h2yj13Mro)THpXKl> z8Hbzzo*TgP19))&FA3nK0lX}Lmk02Q0A3Zqs{?pV0Iv<;^#R-j@a6#C8o)!9d(MCF zc*O?rxBwm>z!L&^QUFg5;3)w-HGqc$cv=8Y58xRAJTri21@N2zo*TgP19))&FA3nK z0lX}Lmk02Q0A3Zqs{?pV0Iv<;^#R-j@a6#C8o)#E2h9Hf9v8sl19(CJPYU440X!vu zrv~tF08b0x=>a?=fM*8q(1&}DXPmrW=lwe&?eJF={mV^p{MS?{ rV^t zX9n=<0A3%!y?^(^tv4?3pWB}lz*7TwdH~M~;8pkjbG@|zyg7i!-uKUW5(0Qi08b0x znE^aEfR_aD@&H~P!0Q8eYXFbS|L1W?3gD>$JUxJC1@Qa;UK+qF0(eaTHvv3U@X!5? z58%lGJRHC?0(edUFAm^k0lX@J*9P$B03Lt;KaYd=`=;*s?){Fb%QFJ< |@XP?78^B8fczFP?4&e0xyfuKw$>*HzI3xw|)Bv6yz`f5`-Te6h z{_+6seID!PsSohC2Jkrf9a%R|QUFg4;OPN8D}d(*@X`QY5x{E#cxwQUliyo)` ew)k7Uijzp#ryuE>#q&) zHwW+(`Cg8j$NN5(%f0XMxZL}`k;}dJ)?My>&%@>3_Yz$0eLukE-h28k_ujvDx%VEZ z%f0t`UGBYC>vHe?R+ksQ^3TVsOg#^*kZ0bsNm2Jjnx_WvZ~#vW;_CXV^QQ;!i~yb) zz|)_J_E+c62;eyZJU4*Hsq;(cj}PDp0X!*yCkOCcb^pIu9j}rAUK_xZ)%}LflOUgm z(EQOnRz5dzdAhovd;~Gx=O&o}-21&8%A@@`%0F4 iVMnA$7l3k38Ogaf%P8 z%H_X!#V=4iNAbNC536z0yg7i!s{1AFPYB>C0X!{$r>grUoj*g}hiG1 NRi616 z8}B$~1@MvpUKPMi0FP7mb-Lb^0G<)R^8 @TDt}n EXD1=oMH=e6hBS1Cs*;U zDo?)R$0}Z|_#nkg6hBV!QpL|x^_D4ae}T#dl`Ed2@>D23Oz|qk?VDG&uv+nP%3q`S zc*Tw4|53bI@rxC2Rs2%LLvKXqZHD5pil?e^h*NyI@+T;ssd$p&I~7k>yhia9#cx$S zRq<@a!;0Ujc$(s$DxR+R-HK-@{=MRviqBF!OYuC#a}<9-@m$3pQaoSrh~mYHKdpF) z;&T-*Rs1={%M^b>@p8pqRJ=m*R}`;O{8h!P6@N|f8pYpIyjJl=iq|Xtj^ak~cNK3| ze5vBCidQLa-icn1o>Dx%+;Up8)ie(!D885SCn+AM0+JPPuly;BcThZ4@fgL!iqo~k z`!7v#?>Et?EM4(^Ju|i$iXWrenW^}Rif1X_N#)5=ytCrDig!^wU-4?iixuyxc!}cO z6faeLtZHYO;$0LkSG=R*6^f@QUZwZ}s@`hFdnjI`cu&P^6+ck%dc{YpdX3^gDu1)$ z@rt)9-l%wJVRYUeq &s98C@qES4RP8KQ ze5CT1D1NHqrHXG !R(xO8{u;&4P`p<0u*y@f_!z~F z;%6$}toT`qw< eMSq2g7Frz>8qc(Tf0qxgl&U#s{ fmfp3vLT`QNJiv5H@&c%0&wD;}?Sli~@APgFcf@hcQhR{ToE zQxxy2+MlZUV8z3VZ&P{F6d$Dg>5BJKJVWuTRGv)5Cn=t#_(>{Hj^bA b+FzsiP0C-Z_|1yf zE54V?V-!DM@n*%dRGwDFe^xxSC_4ZDR6JJkTNIB|e2U`niq|Qgp!jWyCn;X5c(UTh zs(z&?o}>J!iceEKtoZGUrzw7i;^~UFQ}t#jK3(}U6(6tsS&BC+e~#jJDW0o%uHyNM z&rrNr@p}|6QGBN2rHT(x?JQIL2jwqU{3FFH6knlumEwOXUak1qs@@vKJ1JhP_`NDm zz2f&NZWPZ~yjk%A#ak6WLe(2u9G(AvD}Suwhbw=a;`b|myyAt5Cn)~8;z^1ZDW0tO zgNmmpUaWYk;<2ioVZ~=Ff12VCE1s_S*NSH-e!R+`srV-4&r {4 z{4vFg6@Ogu62(gtFID_Ms{Lh(KcW2Pil3tV6^cKp{8fs_D}S}(&nSP5;scbwR`F+* zzh3cD#f{?MD&DO4JjGiTU#oaXo@ed@wp;O7#h+I^PVwIrk5{~p>Q{o|?G;Z_{3Vqq zS@Dk*Pf@%~@l?fMRy?fu0>#r5@1fe0uK4T9pP~30if1Zbu6UN>3l+~%e1xhuSMhF& z=PUlE%2TZP+lrSczK_aNs`z`#U#9r!%3rSd66LQ@yi)Nh#ot%FTJg<_*C_sh; U9fAMrBk*gt-hYK> z{t_E5j9GWsxuI}gRrX$OHQ|{nVwZDWZ9_Hz?`Z4$BR1U*3z0vQN_I4EY-?*PW=(&` z?r2=+>%CYn^L2aH3w_;z^*mqG3tc-JXZw0@)-!xf4{dfdPWE*y>xsVJm-TpGcVs=% z*PU1o@^xp{eSF=8b$4HPW!=u#-B@qm?e#Z~^+sQJXT8qX`>|f;>-||T^z{L(=lQw^ z>)F2U$$Eyb=|w5@-`DZ1C;Ivz*5iGBFzb=NrWcrYG!F9hp{)D(nqDZ{(b(PBy;-;O z^ %&whxqjlLeldY!M2W4+AR zgIO>1_3^Cd`FaTJ*}fjidWNr0U_IH_DXb^@dKl~Rz8=ncq_0n8J;>Mef(!cZYkIK- z$KTf{vu@|>Q&?~R+v|TS>y5r1$$FiyPi4K#*Qc>w=<89e=lPmmK*90%_35l<`1%ai zlYJd#J<-==SdaJhnXE_p`YhIid`&Ngp#Q!e$GW?(&t~1u*XOX_{+HMPG}aq^J)ZSC zU!Ti*nXl=EjU9~(eSJRbdA^>&dbY1GU_Hav|6x7Z*XgV$`ualF<9&S*>yf^`nDroE z(~AV?zppQ4-QCxhv2N$<%UN)9Zf*>y5si$a #JGM@bxvUC;OT%Ksy>I`ubYd<9&S{>yf^`p7kJK(+A$@zprm(-QCwWv2N$ ?J=@n)S %xS*+*z zI*;{iU*F4mhOh5qJ=xd!tS9=qfc1D^-_LrauM1fZ^7R9(`}n$ub$4Gs$hw`cA7Z`z z53m2ltT+04HtTi1ewg(#Uq8Znp|2lhJ J(( zvL5N{r&tg2_0z2T_ J?cD{at z_4ePq{+F}f=<9{7*ZKNQ*2{eT7VCw+ew+0?UoT=k+t-U(&+zp-tS9@rg7rjSzsq{O zuis-m($`B^5AyX=)_r_k$-29*-)G&<*B`Ln{;SvjD%Kl)y^QrbUw_DYnXi|#Ug+x; ztmpZ9CF|L~Ud4KbuUE64?CWaQ6Mg*=>+!z+nDt0sf5Li@uRmqo$Jc9EclY&L*6n=# z8SCvky#Cj)-stOftk?PabJojz{RQiVzW$Q+JYRpsdbY2>W {zS>sr =?^yTo_4lm1`}zmg?R;Iwdi!>-|Mjdl`g$Ylb-v!jdYP|(WWCVW zn_18E^%mB%ef<;b8NO~{J=xcW^+aDcvL5g2pIMLeb%gaGU;o0okFU3~?(XYttlRmz ziS_noum8=gH~M-z>vg`~!Frjme`USU*T1oz=j-2D&-V2ntY`STh4o}#x3Zq-Y5a9Q zx-0#4J}Sh2pZ|j%;gnBu^reoT;OHA2UF7IIN6&Gz>!0tGS2}u;qZj;t)c;$&TmM`q zzpMYZ{qIl70V zyEr<=(Yw0rIsbom^j1f2arF0&{>ss79KF)fm5yHI=mn0R@94RXp5y2uN9Q?ux}&E! z`bI}ja`dH+p5W+ljvnpk5sn_>=mC!I>*!vN?&0Vzj*fBkuFlT!cl1_AZ*lbZj{eHg zYaG4O(Up!~ hIQm9MPjd97j-KG?agHAC=n;+{;^+a6 z?(67Yj_%>;E{=|I^sY|M@ptrAM{jZT_m2L`(Q6#N($STUUgYQnj-K!6xsIOW=pskw zIeNOIr#SjXM^AF}rH-E9=y8r7?dTDX9^&W$j_&K|UXJeJ=q`?qarCZ^&hdBjR!47f z^!JYb%F$~az0%Q@j$Y*G1&*HY=(&!b Sa(F+_s-_dg& zJ;%{Sj?Q!RbVpBd^o@?5 hIQm9MPjd97j-KG? zagHAC=n;+{;^+a6?(67Yj_%>;E{=|I^sarJ qt`fkrK2kyy~xoE z96jIBa~(a$(M68VbM$maPjU2(j-KS`OC3GI(c>IF+R-B%J;c!i9NpK^y&T=c(On!J z x1Y(ax5!3W*pf;v;my3LgEw#5|lw#npU8(C|% z<9XQoNqF=^u?OJ85%U2)AvEhX-i6N$Oc^l0S5dIs?8Jwtl>2SCsO~$rbO@RIlwpH4 zM83BXE6v9Yn#^9*s=|{J%@~AGmD}-oG;JFbPvT~86g}-s+l_Uizr{b@N!?mzb-3Vr zvufRNpeFM^wv^( y5!dgrzXE_ONZOcB` zoSJ!ZXjWBrcW!-QPF%Ps=9>}chT7t20H#Porb{i8B-M>l#U!a>obU RLL6EP696E2L!X@^N%#KSihNu|d3<1tgrS@@JAT*!s+P();D zxS)1y!H?q#c7_X9n}ZQS =l8enX}7W2ivw%MQj$>A@k@G!zDtW?5NB)k}!T3qs}?NM@m47WU<%bQG` zv@lbAS@4C6?o3QEdy8}1C|l`I*wPrC%+4lrx@0_=B2aTb<_%^b8cDjRWb7?^Z)oCO zxf2a$Z{D_<-LW?BPq@rb`8Mdt545+L_P(TXUjK=$<8y3jkS;=7A|DQ_8d*sz! RZ)sfhF}k;!s{G{_+mBh7 zQ#0raG91mMMm)^fn#{-IxI!H9CY~Jd#+kK$po`c`H64Vd(CT*$YA2j*ww`K}jpk%6 z;|g}0^^EGw=h#voU-=n}Mjp;O?-t=(x*g%B+|22hCg$Qug$v(KET(P$-E@75G0!!j z<%O>$W&(~yVFtG2hW(y>C6bPrxhmdl{D_VrWgSzn%FIMos_sNo_kJRdJoWxE QKPbSqq<0==qFmJQS{R>XjHdZ z^by*aqPybFS$m&IJDttvBYCF-+L~xqLePyvn^}TAsxZea#9rerEI`zb7X+u7jz~a; zuqhUnZtf-Pt-QKXKN_m(L|mFkUraW)&$2zaLfmmCo%V1xmiq&}m;!9a&l3A!vB#VK ze&Asoi1kGRGfWpLOET@TM-BZu6;1h)?|PPU_i2pM^%ym7wI}QB-#UaMpHf$1%}PYk z(HRh9JC|hMV$x<_#g;s&bX1-ZkzzWCG1=TNEZ*eGUYwaqdpN#p{X=^d2sQ6es`L%% z>*X`;(4651j*z`rGgwj-QVKgX$zqT9?OlC)2eDt`*LcNhe@t8MvGvrQV*3(rzLLE- z^9k*tsYh_&(!_ka2rUMr;dq@3>&(3kH0Lf!%%U5dt~hq#nadMR+OO!#Rx<{dZSUNf zi(8b&4{) B3G{>^NaZseHX9Ur%PzntjFC4$yDS zZ;Nefe!+rkFQ1s7VARrD#Xc3bLKu}-NNl-cF9>^D#TH9!zG8O@%T_E?SO&A~#AG^k z&*Owm=Hd_yev>(Gxjh+A)TV(F*$ wnwu|MB|& zp}qdMn5EdG8eibp7W10uM@2s^dY0%1L}!b>Q}k7$ZxDUH=u1S85 ohbSk z(Fce=RCEW?okjnKiwKR_&Skd!n??U3`b*K@iC)3F$?W_Q^TjUb9Uga-;??9iE& zN&sU~BHkM7rMu_8?7cHF!5kuc@us`juNC{esQr&&l$z|n$X>kJM0?0ST