From c9a6b692cfbf778930bb377fcb235444f9d6786b Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Sat, 7 Mar 2026 21:53:51 +0530 Subject: [PATCH 01/25] feat: implement private user vaults, OTP reset, and secure login --- .gitignore | 12 ++ app.py | 412 ++++++++++++++++++++++++++++++++++++++++ models.py | 29 +++ requirements.txt | 22 +++ static/img/js/main.js | 65 +++++++ static/img/js/upload.js | 57 ++++++ static/logo.png | Bin 0 -> 110448 bytes templates/base.html | 318 +++++++++++++++++++++++++++++++ templates/gallery.html | 202 ++++++++++++++++++++ templates/login.html | 263 +++++++++++++++++++++++++ templates/upload.html | 267 ++++++++++++++++++++++++++ templates/welcome.html | 94 +++++++++ validators.py | 37 ++++ 13 files changed, 1778 insertions(+) create mode 100644 app.py create mode 100644 models.py create mode 100644 requirements.txt create mode 100644 static/img/js/main.js create mode 100644 static/img/js/upload.js create mode 100644 static/logo.png create mode 100644 templates/base.html create mode 100644 templates/gallery.html create mode 100644 templates/login.html create mode 100644 templates/upload.html create mode 100644 templates/welcome.html create mode 100644 validators.py diff --git a/.gitignore b/.gitignore index b7faf40..c23069c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] @@ -205,3 +206,14 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ +======= +.env +.venv/ +venv/ +__pycache__/ +*.pyc +instance/ +.pytest_cache/ +vault_core.db +.DS_Store +>>>>>>> aa59e79 (feat: implement private user vaults, OTP reset, and secure login) diff --git a/app.py b/app.py new file mode 100644 index 0000000..5d620d0 --- /dev/null +++ b/app.py @@ -0,0 +1,412 @@ +import os +import hashlib +import smtplib +import random +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +from PIL import Image, UnidentifiedImageError +from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify +from werkzeug.exceptions import RequestEntityTooLarge +from werkzeug.utils import secure_filename +from werkzeug.security import generate_password_hash, check_password_hash +from sqlalchemy.exc import SQLAlchemyError +from dotenv import load_dotenv + +# AUTH IMPORTS +from flask_login import LoginManager, login_user, logout_user, login_required, current_user +from authlib.integrations.flask_client import OAuth +from itsdangerous import URLSafeTimedSerializer + +from models import db, RecoveryEntry, User +from validators import anonymize_filename, is_authorized_upload + +# Load environment variables from .env file +load_dotenv() + +app = Flask(__name__) + +# --- Configuration --- +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///vault_core.db' +app.config['UPLOAD_FOLDER'] = 'static/img' +app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 +app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'fallback-dev-key') + +db.init_app(app) + +# --- Auth & Session Setup --- +login_manager = LoginManager() +login_manager.login_view = 'login_page' +login_manager.login_message_category = 'error' +login_manager.init_app(app) + +@login_manager.user_loader +def load_user(user_id): + # FIXED: Replaced legacy Query.get() to remove the SQLAlchemy warning + return db.session.get(User, int(user_id)) + +# --- Google OAuth Setup --- +oauth = OAuth(app) +google = oauth.register( + name='google', + client_id=os.environ.get('GOOGLE_CLIENT_ID'), + client_secret=os.environ.get('GOOGLE_CLIENT_SECRET'), + server_metadata_url='https://accounts.google.com/.well-known/openid-configuration', + client_kwargs={'scope': 'openid email profile'} +) + +# --- Email Token Setup --- +token_serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) + +def send_otp_email(user_email, otp): + sender = os.environ.get('MAIL_USERNAME') + password = os.environ.get('MAIL_PASSWORD') + + if not sender or not password: + print("WARNING: Email credentials missing. OTP not sent.") + return + + msg = MIMEMultipart() + msg['From'] = f"Recovery Vault <{sender}>" + msg['To'] = user_email + msg['Subject'] = "Password Reset OTP" + + body = f"Your One-Time Password (OTP) for resetting your password is: {otp}\n\nIf you did not request this, please secure your account immediately." + msg.attach(MIMEText(body, 'plain')) + + try: + server = smtplib.SMTP('smtp.gmail.com', 587) + server.starttls() + server.login(sender, password) + server.send_message(msg) + server.quit() + except Exception as e: + print(f"Failed to send OTP email: {e}") + +def send_verification_email(user_email, token): + sender = os.environ.get('MAIL_USERNAME') + password = os.environ.get('MAIL_PASSWORD') + + if not sender or not password: + print("WARNING: Email credentials missing in .env. Email not sent.") + return + + link = url_for('verify_email', token=token, _external=True) + + msg = MIMEMultipart() + msg['From'] = f"Recovery Vault <{sender}>" + msg['To'] = user_email + msg['Subject'] = "Verify your Recovery Vault Account" + + body = f"Welcome!\n\nPlease click the link below to verify your account:\n{link}\n\nThis link expires in 1 hour." + msg.attach(MIMEText(body, 'plain')) + + try: + server = smtplib.SMTP('smtp.gmail.com', 587) + server.starttls() + server.login(sender, password) + server.send_message(msg) + server.quit() + except Exception as e: + print(f"Failed to send email: {e}") + +IMAGE_EXTENSIONS = ('.png', '.jpg', '.jpeg', '.gif', '.webp') + +@app.errorhandler(RequestEntityTooLarge) +def handle_file_too_large(e): + flash("Error: File is too large. Maximum size is 100MB.", "error") + return redirect(url_for('upload_page')) + +# ========================================== +# PUBLIC ROUTES +# ========================================== + +@app.route('/') +def welcome(): + return render_template('welcome.html') + +# ========================================== +# AUTHENTICATION ROUTES +# ========================================== + +@app.route('/login', methods=['GET', 'POST']) +def login_page(): + if current_user.is_authenticated: + # CHANGED: Redirects to home (welcome) if already logged in + return redirect(url_for('welcome')) + + if request.method == 'POST': + email = request.form.get('email') + password = request.form.get('password') + + user = User.query.filter_by(email=email).first() + + if user and user.password_hash and check_password_hash(user.password_hash, password): + if not user.is_verified: + flash("Please verify your email address first.", "error") + return redirect(url_for('login_page')) + + login_user(user, remember=True) + flash(f"Welcome back, {user.name}!", "success") + # CHANGED: Redirects to home (welcome) after successful login + return redirect(url_for('welcome')) + else: + flash("Invalid email or password.", "error") + + return render_template('login.html') + +@app.route('/signup', methods=['POST']) +def signup(): + name = request.form.get('name') + email = request.form.get('email') + password = request.form.get('password') + + if User.query.filter_by(email=email).first(): + flash("Email already registered. Try logging in.", "error") + return redirect(url_for('login_page')) + + hashed_pw = generate_password_hash(password, method='pbkdf2:sha256') + new_user = User(name=name, email=email, password_hash=hashed_pw, is_verified=False) + + db.session.add(new_user) + db.session.commit() + + # Generate and send verification email + token = token_serializer.dumps(email, salt='email-verify') + send_verification_email(email, token) + + flash("Account created! Please check your email to verify your account.", "success") + return redirect(url_for('login_page')) + +@app.route('/verify/') +def verify_email(token): + try: + # Token expires in 3600 seconds (1 hour) + email = token_serializer.loads(token, salt='email-verify', max_age=3600) + except Exception: + flash("The verification link is invalid or has expired.", "error") + return redirect(url_for('login_page')) + + user = User.query.filter_by(email=email).first() + if user: + if user.is_verified: + flash("Account already verified. Please log in.", "success") + else: + user.is_verified = True + db.session.commit() + flash("Email verified successfully! You can now log in.", "success") + + return redirect(url_for('login_page')) + +@app.route('/logout') +@login_required +def logout(): + logout_user() + flash("You have been logged out.", "success") + return redirect(url_for('welcome')) + +# --- OTP and Password Reset Routes --- +@app.route('/send-otp', methods=['POST']) +def send_otp(): + """Generates an OTP and emails it to the user.""" + data = request.get_json() + email = data.get('email') + + if not email: + return jsonify({"success": False, "message": "Email is required."}) + + user = User.query.filter_by(email=email).first() + if not user: + # We pretend it succeeded to prevent hackers from guessing emails + return jsonify({"success": True, "message": "If the email is registered, an OTP has been sent."}) + + # Generate 6 digit OTP and save securely in Flask session + otp = str(random.randint(100000, 999999)) + session['reset_otp'] = otp + session['reset_email'] = email + + send_otp_email(email, otp) + return jsonify({"success": True, "message": "OTP sent to your email!"}) + +@app.route('/reset-password', methods=['POST']) +def reset_password(): + """Verifies the OTP and updates the password.""" + email = request.form.get('email') + otp = request.form.get('otp') + new_password = request.form.get('password') + + if not email or not otp or not new_password: + flash("All fields are required.", "error") + return redirect(url_for('login_page')) + + # Verify the OTP matches the one we saved in the session + if session.get('reset_email') != email or session.get('reset_otp') != otp: + flash("Invalid or expired OTP.", "error") + return redirect(url_for('login_page')) + + user = User.query.filter_by(email=email).first() + if user: + # Hash the new password and save it + user.password_hash = generate_password_hash(new_password, method='pbkdf2:sha256') + db.session.commit() + flash("Password reset successfully! You can now log in.", "success") + + # Clear the OTP from session so it can't be reused + session.pop('reset_otp', None) + session.pop('reset_email', None) + + return redirect(url_for('login_page')) + +# --- Google OAuth Routes --- +@app.route('/login/google') +def google_login(): + redirect_uri = url_for('google_authorize', _external=True) + return google.authorize_redirect(redirect_uri) + +@app.route('/auth/google') +def google_authorize(): + token = google.authorize_access_token() + user_info = google.parse_id_token(token, nonce=None) + + email = user_info.get('email') + name = user_info.get('name') + + user = User.query.filter_by(email=email).first() + + if not user: + # Create user automatically, mark as verified since Google vouches for them + user = User(name=name, email=email, is_verified=True) + db.session.add(user) + db.session.commit() + + login_user(user, remember=True) + flash(f"Logged in via Google as {name}", "success") + # CHANGED: Redirects to home (welcome) after Google login + return redirect(url_for('welcome')) + + +# ========================================== +# PROTECTED APP ROUTES +# ========================================== + +@app.route('/upload') +@login_required +def upload_page(): + return render_template('upload.html') + +@app.route('/gallery') +@login_required +def gallery_page(): + entries = RecoveryEntry.query.filter_by(user_id=current_user.id).order_by(RecoveryEntry.created_at.desc()).all() + return render_template('gallery.html', entries=entries, image_extensions=IMAGE_EXTENSIONS) + +@app.route('/ingest', methods=['POST']) +@login_required +def ingest_record(): + file = request.files.get('file') + narrative = request.form.get('narrative') + + if not file or file.filename == '': + flash("No file selected.", "error") + return redirect(url_for('upload_page')) + + file.seek(0, os.SEEK_END) + file_size = file.tell() + file.seek(0) + + if not is_authorized_upload(file.filename, file_size): + flash("Error: Invalid file type or size.", "error") + return redirect(url_for('upload_page')) + + secure_name = anonymize_filename(file.filename) + safe_display_name = secure_filename(file.filename) + is_image = file.filename.lower().endswith(IMAGE_EXTENSIONS) + + try: + file_stream = None + if is_image: + try: + img = Image.open(file) + img.verify() + file.seek(0) + + img = Image.open(file) + data = img.getdata() + clean_image = Image.new(img.mode, img.size) + clean_image.putdata(data) + + import io + clean_buffer = io.BytesIO() + fmt = img.format if img.format else 'PNG' + clean_image.save(clean_buffer, format=fmt) + + clean_buffer.seek(0) + file_stream = clean_buffer + except Exception: + file.seek(0) + file_stream = file + else: + file.seek(0) + file_stream = file + + sha256_hash = hashlib.sha256() + current_pos = file_stream.tell() + for byte_block in iter(lambda: file_stream.read(4096), b""): + sha256_hash.update(byte_block) + + file_stream.seek(current_pos) + digital_fingerprint = sha256_hash.hexdigest() + + os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + destination_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_name) + + with open(destination_path, 'wb') as f: + f.write(file_stream.getbuffer() if hasattr(file_stream, 'getbuffer') else file_stream.read()) + + entry = RecoveryEntry( + stored_filename=secure_name, + display_name=safe_display_name, + narrative_text=narrative, + file_hash=digital_fingerprint, + user_id=current_user.id + ) + db.session.add(entry) + db.session.commit() + + flash("Record secured successfully.", "success") + return redirect(url_for('gallery_page')) + + except (OSError, SQLAlchemyError) as e: + app.logger.error(f"Error: {e}") + flash("System error saving the record.", "error") + return redirect(url_for('upload_page')) + +@app.route('/delete/', methods=['POST']) +@login_required +def delete_record(entry_id: int): + entry = RecoveryEntry.query.get_or_404(entry_id) + + if entry.user_id != current_user.id: + flash("Unauthorized action.", "error") + return redirect(url_for('gallery_page')) + + file_path = os.path.join(app.config['UPLOAD_FOLDER'], entry.stored_filename) + + try: + db.session.delete(entry) + db.session.commit() + if os.path.exists(file_path): + os.remove(file_path) + flash("Record deleted permanently.", "success") + except Exception as e: + app.logger.error(f"Deletion Error: {e}") + flash("Error deleting record.", "error") + + return redirect(url_for('gallery_page')) + +if __name__ == '__main__': + with app.app_context(): + db.create_all() + os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + + app.run(port=8000) \ No newline at end of file diff --git a/models.py b/models.py new file mode 100644 index 0000000..7bbaa33 --- /dev/null +++ b/models.py @@ -0,0 +1,29 @@ +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime +from flask_login import UserMixin + +db = SQLAlchemy() + +# NEW: User Table +class User(db.Model, UserMixin): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(150), nullable=False) + email = db.Column(db.String(150), unique=True, nullable=False) + password_hash = db.Column(db.String(256), nullable=True) # Nullable because Google users don't have passwords + is_verified = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + # NEW: Link User to their uploaded entries + entries = db.relationship('RecoveryEntry', backref='owner', lazy=True) + +# EXISTING: Your Recovery Entry Table +class RecoveryEntry(db.Model): + id = db.Column(db.Integer, primary_key=True) + stored_filename = db.Column(db.String(255), nullable=False) + display_name = db.Column(db.String(255), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + narrative_text = db.Column(db.Text, nullable=True) + file_hash = db.Column(db.String(64), nullable=False) + + # NEW: Store which user uploaded this file + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9d6e1af --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +Authlib==1.6.9 +blinker==1.9.0 +certifi==2026.2.25 +cffi==2.0.0 +charset-normalizer==3.4.5 +click==8.3.1 +cryptography==46.0.5 +Flask==3.1.2 +Flask-Login==0.6.3 +Flask-SQLAlchemy==3.1.1 +idna==3.11 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.3 +pillow==12.1.0 +pycparser==3.0 +python-dotenv==1.2.2 +requests==2.32.5 +SQLAlchemy==2.0.45 +typing_extensions==4.15.0 +urllib3==2.6.3 +Werkzeug==3.1.5 diff --git a/static/img/js/main.js b/static/img/js/main.js new file mode 100644 index 0000000..0a00f48 --- /dev/null +++ b/static/img/js/main.js @@ -0,0 +1,65 @@ +// Init Theme +const saved = localStorage.getItem('theme') || 'light'; +document.documentElement.setAttribute('data-theme', saved); + +// Auto Dismiss Alerts +document.addEventListener('DOMContentLoaded', () => { + const alerts = document.querySelectorAll('.alert'); + if (alerts.length > 0) { + setTimeout(() => { + alerts.forEach(alert => { + alert.style.opacity = '0'; + alert.style.transform = 'translateY(-10px)'; + setTimeout(() => { alert.remove(); }, 500); + }); + }, 10000); + } +}); + +// Theme Logic +function toggleTheme(event) { + const html = document.documentElement; + const current = html.getAttribute('data-theme'); + const next = current === 'light' ? 'dark' : 'light'; + + if (!document.startViewTransition) { + html.setAttribute('data-theme', next); + localStorage.setItem('theme', next); + return; + } + + const x = event.clientX; + const y = event.clientY; + const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)); + + const transition = document.startViewTransition(() => { + html.setAttribute('data-theme', next); + localStorage.setItem('theme', next); + }); + + transition.ready.then(() => { + document.documentElement.animate( + { clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`] }, + { duration: 700, easing: 'cubic-bezier(0.25, 1, 0.5, 1)', pseudoElement: '::view-transition-new(root)' } + ); + }); +} + +// Modal Logic +let deleteUrl = ''; +function openModal(url) { + deleteUrl = url; + document.getElementById('deleteModal').classList.add('active'); +} +function closeModal() { + document.getElementById('deleteModal').classList.remove('active'); +} +function confirmDelete() { + const form = document.getElementById('realDeleteForm'); + form.action = deleteUrl; + form.submit(); +} +window.onclick = function(event) { + const modal = document.getElementById('deleteModal'); + if (event.target == modal) closeModal(); +} \ No newline at end of file diff --git a/static/img/js/upload.js b/static/img/js/upload.js new file mode 100644 index 0000000..bcc3610 --- /dev/null +++ b/static/img/js/upload.js @@ -0,0 +1,57 @@ +let currentObjectUrl = null; +const previewContainer = document.getElementById('preview-container'); +const defaultText = document.getElementById('default-text'); + +// Elements to toggle +const img = document.getElementById('preview-img'); +const fileCard = document.getElementById('file-preview-card'); +const extensionBadge = document.getElementById('file-extension'); + +const nameSpan = document.getElementById('file-name'); +const trashBtn = document.getElementById('trashBtn'); +const fileInput = document.getElementById('fileInput'); + +function handleFileSelect(input) { + const file = input.files[0]; + if (file) { + if (currentObjectUrl) { URL.revokeObjectURL(currentObjectUrl); } + + // UI State: Show Preview, Hide Default Text + defaultText.style.display = 'none'; + previewContainer.style.display = 'flex'; + trashBtn.style.display = 'flex'; + nameSpan.textContent = file.name; + + // Logic: Is it an image? + if (file.type.startsWith('image/')) { + img.style.display = 'block'; + fileCard.style.display = 'none'; + + currentObjectUrl = URL.createObjectURL(file); + img.src = currentObjectUrl; + } else { + img.style.display = 'none'; + fileCard.style.display = 'flex'; + + const ext = file.name.split('.').pop() || 'FILE'; + extensionBadge.textContent = ext.toUpperCase(); + } + } +} + +function clearFile(event) { + event.preventDefault(); + event.stopPropagation(); + + fileInput.value = ''; + if (currentObjectUrl) { + URL.revokeObjectURL(currentObjectUrl); + currentObjectUrl = null; + } + img.src = ''; + + // Reset UI + previewContainer.style.display = 'none'; + trashBtn.style.display = 'none'; + defaultText.style.display = 'flex'; +} \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..488e1fd0c97805844d8aef2b7f2ad4bf6d3c781b GIT binary patch literal 110448 zcmd>m`6FA|`+qboZ7rD@GqxZ)rc6sI+Snqrri`(qttzc0NNu&%P9;ILR0(ZqX)U2z zswisTf+DetJq@*s*eW7PBZ#lg`?Gw%f5SKDhkNe1&pq3{=Q*$E^*lETSFKD%Ps*GG z005$9fB$tI0621(Jpwo`cz9rikMa%=0zubJF9SXf$gUioTy?WEbGNhvoIT7R2OJUj z1pxdZa+m<|UVx+j<^ceu!y^Cy%oX_GtGP%1`|1%uF7SWzKLn*uXJ#H&a?9(6U9g>{ zg&xY^SIy;)zpI;CxbM9m0svUJ-eK0)E!agN+}Fo1NH5&r_kShy4)Z^X)qhv`S0vcm z;CDOAs|v>cfo=*=wexD{e}|t`P*8vc-f`Ev{+G#r-F|px@VjSl@I5_s^{}uowJ{%cr=162QUMO{Pfy!!vteRwPEN2wkn(97+xuLIHY@C9W=6iwICr^HA zcW-@oJTB?J$Wcop$)lo|ewqz^aRzJ;4Lx=07Sq`OjCB#(gTv2`jM6@+4<7iQ9jUcx z*}eXvqNSR1e=~&hB@(|G5L7`YtNj0)uBwz5YTiGPvyU-IG30YYCbkolb12($ps4Vf zwpbyMLw%sz<|AQAj;z7`V5wQKf0KrH?rJ{FEq`F6c7~vX;j!K$@0_SkaaYo06b0%tzzq{E&_W8K}rE>X0jokN>tiyyhyfND# zy0GhFba0z^kG(o(5XJF`sySFG#KY06qeYX0+4|q}Y639jbNP|qzsW&I;y|UPr3U1? z%ve!~eetp3KVG#KwkXY`+5K!r{E)`~*ubRh^ui;yU(E_UHQJvtR(wF;ft$O?|JE41Iw zX`E);mrNaE&x6W~1jHilZ?i%N06HcW(BEJO}v|i+U zBllcKB=+EY)B!!LW%sK-GHRcVsKKpzOwRAmZ7&bveZbU;8W%ZdMeocJ#+}JE%qi{f3iQz0=)|y9?RgaHKy$y&Cwn+IsQt8n%)3uA&l9 z1*D1Eq~FNlt!z$5ZZ=bCpX&|x+6`fo)8`ube`G$%{`MQ!_IeB!5DrGtYmS%g)BO3g zXOZ9PmyIGH=u@d4)Zcd@RM$Oc`-hu;%vj&N#?o_ly;awJPsvM!c|xsts!3`zRhEy4 zpO}bT4)e)ZsacN^u|B2O7*=^IDNgARB(?t*=3aj+*6&HOK%d}?8Z+_OFsa8eYM4DE zYeneiK4eR5JLdr*;-*hBV=D%Uo>vUADdY8_HLa+hC#JbPfp$gr2Rvq4B;a0hE=hI{ za4aJ>pkpz=X-98{85hv7Rd&yJYogV-V~5o)wf$9Ozm)`C7W|}fb#SLo2SCch5lU1R zLPe-m^{uA5GoD2UGaIil6>NOeM)ibR%}zkbs>1xG&tX@SgiHimR_`EhA4FV@p*cz! z?T{1FOCWmZHj8~l(h)hZ2!8!?P9wv-g=OxL@-F`4y@_t8n624|ibVlk=khOA zaw`}ND}b;;=AB(nXDL|(mN9gN_PN{D4DxrMpwSHU&niilfcCw;diw7p1ERNgJ1F__ zaE>eY0k+E6)WFM)8a*d4+71M0FidlN6uOd9;K4^=+nNP|`_@t3@6|Ail%IsJ&SZ`K z){ZdXUI_ek^yl^&O0-Rcut_pR`x0-_6!LoS*#3ieL+{iskRHOTFr)(fdhL#0hq=?R zL#A6Rw|h*_#oW@XYd9S{`(;*FkT}Zq{nK`U{F;8b5dsCB8=tC}Z{P9VN1F=b7o)Rw zPux@evP|gpHWFhAl${I6R*ubW&K1Jz4AU?0F1HtEu77#C9Oe7rfU<`v{OVEodfhn@ zQ`gxPzQ^6NrBVa7Uf4Q)8J=qeCJM>#(rhY(GVWi!w)xCdUb-u--E({XZshm1JyeQ8 zXu(qRJ6ZPEEu3vG1mM>cH_0JOWEixt?OQ_L>gsfPd@nT^ryn$*>j70iqW#@ifvC#F zAR8~miQ@ayj!lYY^-o=S(s@Ki&ND}X{D@ZGYTvKMqcr|m?^!LCxzL(|J9fMrg!{1p1mkk0`9HezF> zR)cJuEC=&05Up>MERm!fgNLvl8DidvHn0SsQUGu`@dhN4Xr7!b*GG|4=2-7k%y}i| zVCD1)0=o%wsVO0y61x0Z)Ebv82MjL11Ty5FFAxHRCqp2wfSLkuz#y^t6b{rX+etIS zN1pU?%!W`j)Qk*S74OQ_U2lbK=UEX8$&u$y& zrCLHu{+crKnNrn@);^aA&y>-Y)Yj2H6EENgll!#am)zgIX`cT zWIQiWp9Nyj3%!{+@C&{okA-qQR8bki8BLWb$=EivzZpZlSXyp&;;HI4Vx#F-Wa@?A zh7enJo~iH!g4wntkB{`96*npbMg_#dSCwuGL8A9N7~MAV4P;kO+LHxE$<=&WLj2z( zYm^4^=@1K-%%kB$yN)gqHi&7I(MH#PC z72yIT!8p*%cz8tgSeW>z2dAiIfh1&S|Da2 z$VAN27}7#!;B^0(c4DZ2biKtftvY9HU+U0JC-r~*Ub}f=pX9!K0OeOFXoat<>pLFm zWXjyhLmaW-y-`}Hh=A`bCd3)?_qGS4J~SJAU*XjABdOYbn||8AZURJ*xaas2YMtk2A-cJ^_it|z!Ezvt zPwL1+Evg`KAwt**86uvas!=r|A(G-PIx89JN;d@u^gr`f@QfAX0VXaPiTyzX{Nqq| zswy+Nq6*S33rG?srtmA4d2^{~Ss*q^$X$ez>tRtrsECQf<9H)sgoHUQMAiXC*h!1W zQwFuxks0)SMJan}jiYWW?Lc`{J7@<$zY1E7SKp&2YTR>tYHhgw{#js9%}1It5fAh+paQS}ro^R-eQ{U#=-y6iq?Z%ef#@!Y^ zyA0xo&?ZgRG7Lwc6$j+$=TYUBeDMuIgVB?%9gv$3>!jl{AI*1d$IZvR7!*Cd(FmaD z#b)C&!Lvk@IJw|m-HChK+lym?_Ao{(u-*E1;S*4<>nQ+m-XZuo=Y*Jnt72~^9FrR> zBDZ}<!qFxJQqPL>5jl)wcK}kDV7pd;vL4jazE<2fUrdFf{HDE9t0H>ZA35ngglC|9o}W zNNyR(Yhjp_Qax^1Z3W(F1!xle#c2L`N^(roQhxlophVd`T&%^6GwoT>Rd6uFaz7i% zEzDbRXmgVDI&|47&``JRL)68dOPP#jgSGl*ZenRV*}Aw zg^c7p_cWDc&^)5?H_YRb%e03{8T6myLx5BK0pjgO)3R*M;?(Jxp_43NtLV#GOQu1~ z{$vZL3X{oo-kqO*hCE;+4{kIT@hvCo;KNvV_Q%i#yyvOpssuyW(lYM(1#Mw~`btdw z))6Fl<&5LT)5%$vL0lKHwSVvwK)y=*&-=Vbf|_L^13VRBU@n&UVDNK%DM#%=SCX*k z+mw(2b{Q|G*iaH0krW%J?$u1kV~6RlBrley_%G0bB8aRD@Rdn&w@&QP6UqJ3tCOGX zT4;}f+ZIlGCRXQYM#)+$Hk@%su&a{q^dg|8XRgRUhZx24z+`qg|_mDd; zkUJI&+apcit48>Fafm8@+aY*G2zXoEYt1yCS_DE={hd1!KiLFY4i{E^>X=HB1=!_s z+tp!iL&~GWb7jxmIKQF}0T7vAWtr6{D<|{2m_^P(xcL|*G*Q7i--Aj#d z`iuk4Ma-*Ej1nnv`)?$(gI5i*%Bkr{6-*ggeaybK%aj(r_~sB017;QBTsc3>V>|(8 zldqGD%ac51?1DCSA=Q`3hx9Mz`{p?Z8Is`>kQ~P3B^O;{g9v z_e(kVIn{ai^F(c021G(n!c-Cl)u%gLEw{BQ>9fI8%rX(2t#}Y13d&na;Y;PBjB^NM zq^Q+aZ7Vf3#!)XHZ!CQ_g?9($g^FS4&pfFvLhqhI+}?4>s(GPA>Zw#8&|&x#6VpS7 zy|idZ1IF11CvY&kG&{bE_0GbcM|MDLv`yu&x?7Ry-gwQ8r4=x8dbK0k`}0I3Q#~2S8=b1o{XWHGMyKFzaz1 z935QYBCoKWeUXPI`dSes7h(&<(7~P5ijPKRp_XAgXO^s$WM<~{GT_xAsZ@(v8QH^nsV3+Zoc@8j>La!e=xfPYpR6dnBh z`DlSg%oo+E^RoiZ!Ohp2T%L8u3f_&%Zp5Y*!Z0@)s1%`8MILk)+l|{I+dU;;vNBg7 z$&6h2g)dbHP}B-twZhpWX}O|2uh=PgwZ!bH?Pvw0AuF#LtIDN}%zBq0P|=k?8)E`` zsyLgd1o!0NRA!{OX@bSdP@z2q z?U}UVR2coJ8&h0G3$*`pYB;K4CG&m|pp`KGm@R1dzpDptzI5##&ddV!OJhZOOkm|m zMxS4}>d~&cOGLk^CNGOi^80UB)`0h=-^yRusq2dNS~ZFUK374ZWRr(Fh4wWROeOyb zX*EmvavBuLo>R`gKMqvIHF4%N)b@s-77o6x9+a)>)ys>|u4wJ*>|qzm@7L#wO1%&7 z0Yx0AlFOLT%>`ls>; z&Ad6S1TPC8eepeTx4JNsp(aBy->#jVB!S!eyDwkHXI_;2 zZGD@T4vmw-LbI~aY zEJ}@dm8f|y8_2Vdt40>&i#WO@LABj@5-9I!S2rp>O2j)A`?k&Dz%AN9RKaQ3%R{z8 zb6EqudD_i+K_$#*z#OW~uH&5G2BkhoN$DUHJMcj<5wU7uC%#C?`RQ=!UZF^t=&U%T zS4F)F%7VN&e=#VFa>I$EJuDsk93Q-Dq}Z#Z&aR3l>76JaxQZ}5Ijh{g=lF}a&Y$db z$I@1|7S`6drSiefhRr9M(o!7iGH~k76l?m|uBKDZ+P~A@;k7?#zt{1X-554~SFt@C zgOof4m0y2$sq$wTAcV<cuSd7Wa=J<0N&a4-fcls@`}6X34>Y-4Wyxh2Y~C)uL^tBgtnq@dqtVy( zV!TGGiCQZn7vV&EpthharaNp}V0egiOs#r(_rCU9A{oPQ(lPv+y0dvumV^F6Q7keV z=5>V7CoPBJrs{@s-<;8s2xY_niu!Q4ssIE}+KOEpWw|+54GoYRe<0};?qVm*&9^TR z2CN;|t!@d|lYLm1LM~Jy#=47c{REZb{Fu~g>AGTlvh3`*uM`qxs}dy^=4gVAk8cUB%c=9rfwM)aRn>B7CQf#XeJ30}B`=R+ z8l*Jj>?jBULLg9Tq?eNP0$YRr9IO7fzB^afZ!uZ!wt*)YN_-fn{*ovrhrZ@58OZu% z6+4$CxqbCw(AMqW+qE?kN^v^A@1v-B3m0SD<#i>>C+u>-KWS7tvBm_}#Nk^>-Z|7B z)0C-WK>{d{V0mnT^dJFt9y?N79kx!L*Ya9ou;U%%pmyA%cr}bnz*Bwr9m?=TJZzUZ zq>e_%%ncodG9RY3d0-f)9u!7EgJm-H4Y%5zoW_5l!t2^gi(0GoXU99DNPqPwApM`G zEk(~pj&ws7&P% zz#N-|Da_fge5K62EtiBSjcbxZYy9cmRkhN84KzSsYU8Utmbp`zy z>pn81j4?VOccowYdUU80-#YIL+u;dSl7T(BFO0VrJ;ahv?=s5Yi7|XxBg%xkkQ}yl zLC9#?=8YkevXF_Z^YR?X9w4`tXGrw7qIvl_FFO0+1c=&%Cc|M9nW*uwznvF4DbDLd zLi7bNU__|HxR6y3r$!78PsCN6i)}Kjx`cc^70xsTA>pd`&O3kGh8hNVCDlUrReSS# zLq8tb2b{00v2bWT;f)6yIW=6*f%d$>eJQH6DM6I8WFgiKY7(jn=wBIqT7#-oBvck1 zWDQg^Ar7TW5m0|=gV8dHBuxguDBFuWE|G`FAkOA|3=hWkgCV3({F_gu^^R_};tcl8PAE%7>qz>W27%h4_+ z9h4Dm!~JK%RsFyR8m&F8lm%0`&VqgKrMzb6Sh$n|($mM2-1Af=byI3RvK1=ib2OZ{ zq+pysm_=*YJ_sfYb&c%g`JbQ2Z!ax2G6eLM7^F%%4>uei`cCXJbKF^NHAVbQXacv5 zAvaQXXv7;J)s1kw=*ffeYA$txP_t9jUVxKNloWmli!OB8W7kV9@({9(1 zKhbY1Y71&WQL}=NHmT%gIhmlE@kMuRQ-7jtm&~Phl8K|IYlgTU{84eNgA9K4H5fYF zg-LX1p?iZ;c5Ie28t4K0{dlq?mk&b@TrsO9CYBFnUQTIIh?m8X00m`+t|KJD$W`EMrl^zS`Di(U zSJzOb(yCNq7=YZa9LgLs6(o}mDJw-th)^fzvPkCUd4<&Ug|QYGhCX6^s)qLXBY)J? z&x*lFl;3)GsaM?F*fFnOVzf8q#49jEoRQiOvo0KIv^}I-?5?e~@Yh-}AMR8#_gq$)Ezf-WrQxq6&#AGxLA zqt0nF3&M28!xG-f&TtnS)M9M{CBydTdo`jbqr5TMk?fA++W==LXV-19BRg7RP!Vwa z@%5=2hwG=OWq-rp-|x?rNy|9k@p_$>eqj-v!n}$?0jdZ7_Pyq|uB{jc$Ms>;o`e*+ ztyWeAYMt0B-zA%UxXlS+TD|??#0#MLQ@{xdowhe22!|5$L8S(o064o-XyV!0=f-O# z!erTROhqWmw?p%LpUb@|WN7K3Rw{opngU}UPpN}=iidJ%_HMpMD!V@yCR^|yYB=@O z{^J4j@s8?&MASz+w~M~gey0u3IF<;WP}cN3wO|&Rn!msm-<7C>pCuw_g%EDd-Bb*! zO-tE}r{?Zu?S&~ne%T{mK3eZZ!J$$ueQb8FRHd3IpXA4@#dbrE5QX_oSjuIvc#}vU zGFw)tsm~mF(U8^mwT}Af%b9#^S1C|R_u_o!Vsd`+6{Pe8?5jdTTV89s046x+(6!N~ zj+w5Q@AZO>Y5Fa07_w!UDEUvX@)qVEICM+f%V5OLcBPtP0fAg@i0az&wdHtN`9(8% z4%Br{vQ?Sq+={*n<3gDNETK}v^XN8>&x&F=?t@U)yS6^mDxd$oB`VxB(Gu?+Ip2{` zOIgtOUS?~VkQ(j(eZw|G#brH(+d)->x4&x?)t|~>s7k3yOdUE=rbOxi;X!Tjg61j< zO0hzJYMl;l`jRSCYQS7ghj|&P0f4B5D>KAUMRHk20P~hS%B{i#FAfh;_WL-&A;gxFtTR_mS~yNtm4EA=Jh{Q%rhdUQ^W>RlzMjFdbv@8y z?j?rW(;1~OOmu&u<@-bI*rXT@$Kf=zO~a>_eLgTN*g=ud;1=s>HSZWGC*pHw4jc^k zJ`SM$)Mg4UIpecOnRM``NI6D6b%|9Ig=2!3Y+h4{#_=#u$}snrGg41MHHdvFEu74q zbM)=SvX8p?D2D~ne-za_l*+_v3;tbC-!=p%N0fLn;$tTU-nug*d7Ygf5(ugyHEREu zsaI;yLW7f@>5AjA$Nc#nK`2|~!R=jCbv37_^>S(TUa4zYW$2f5!OcHPn44U0v0Eu; zd$Ft1=CCDuU2I*-$_Tkuy1g)T(dWiWYJa-u(|$3y_fbAt=mA`>t2$mNdMj+R`MV!6 zKK)#fCjK=<&yVK}CggR4sBuvtxe-D@2=R)NC>{vFZ0J7xTQMwOS=~o>F*hINeTh%c zPY0yPgw$R9jV=`ZTgb9KW5?2AG<#~A%gmace#=^ZP(xRC+~t*M4}a33PzqSX{=>;` zsyX+q;ra*E#!I(aO6-?Bn)9r~tPF|?m<2N8wk~1GBkNF&_@@nUC%3L(FOZInE2Fg) zB8cVcaUfqvA11k(KNLd}B)JQ@i$)8Uy95OvVg%#MyirLh`|+1IJ-l+Q3J^8x=3q2ML6wL0o=LsOye3 zW$SSvOE0S7h|}a~F8_j2{Y8mF{;#F@)m}m>+UavqYSdDKGAK)~^HoIXYdtS7ajdY)9+OrZb*@Eq#$J^m{kd z9p|85?mW#>$;Pntb8H3frn4()PN``&DP5ywpL8+4xJ!mBtY?}beiz{U{!^&az9oxh zg9$0g@W*a2QoaSgXT^A1+TeRBn!OU#QdxgW=xLiRglZ{W4+1g(6T%o!ALlYY4i2u$ zm8ZbC$qz12Xbr}AosOeG%81ae#ybp3M-YC5tWtaelTQ+H3#^!f^c_uy!6lt=*righp?jaK|~v z#tZhnAi&UVg)ZvnLYs$7k_vnV?{S_Dkx{8(_R*niT zH%7=e05k=5Os1MGH+d&=uaop|AHIAU*PIX)Q|G6gFDOA%Azz4pV0#yH=I&oVhM!V6 zfoe+gPan`RD`T3%<@}nCs6CwATDujuhV0kx8nrxjS+_q5y6J|bLz3syHt)mgl*-fKh%Ki@;<;iO;javY2=~@5u3TJM; zOvWG`prj$5lL;hfOE)zQE#@niHcJscJsA=x7rLIF*&W{1m6@KqIB1sCpf>wdQTE$T%UC21_+y0s789vIwbEdu)6-g9yE zCu&61$B2z;Ub%e2SX?~hqZSw>tX{Pffbq`ZlEes!H5xD8MXhL*e9aJ%*LZoUx)2%B zyDlvRa9=;MB3$241Qa#g?ie6UINI~5(L=p0aS7)pY#Dm3%T7mt)n#xBrDKujnV*cQ z*cqwKD{j6Z6~X(%aNpA#f&pnUKZr#or0$T)j@b!I>qCI|&XKFJ#PJket()Q4Ut^r& zzy7dQO5R`e-#TG&8=#sk{9vzxnmpGeoy$(}+erB`I2Qn1=qic(SDVq`_msf^5NsHSANnDXWimb-Y*{s zr2wUYIldp&*~2MKnK)ZVL+!>)Iol|E>HG#)OkZd5I`JbOd|IQtte?WHEW^PnB9dGp zY$mX-A>zg!hHRBmC+5;EVYhubfJ#MUIR9i-V?=Z58{L(l;mhrVo!M{aD&G%{UQ+k< zj=Ec3RGTyoN42)c?X}i?$jp4I!9ewHwHYhtsm-1-jarX>#*zJr3{3KZIPvn4r$UO8 z_-A)Mh(1x4g+}Du3m@)Qmgiy)cU-Sn#}jX%%_%Yp-Yiw*^9WHiz#k! zcA|FiB#L)NYoW7*m!rQN!c>0-%u+Cz!pQOE$#%D4_a<&Q=`Bs@Qt`t2VvQMXx z&T={Cc2wIa*3#b2qjhuRk zu&Ljayw%*vjH%o#uGL+<>PW$=l%C|H(L)ZNz1Y6sj(>73$OK6yQCwsC;@}rFs>~qN z*muhC;p@M|vZX;mOY zlR>>JJRrrhf^>P&bP8gzJhRaK)@UvgGQ-DM}`usEqyn;c99hto;4hQCbIE|;=5a;!jO5}r*iN<-iwx#|eH zazd)@icBr1zwr>oi5q{_a4>8y(@RQ`T5Lc+NL@gx~+>+xwl3uEDbP8 zE~rx@f<7ZkyBz`BvHmd6Xuh9OGDda!18_RSk|~Q|KF`sz@{V;JUh$C5hB^VaKczIY zGJN`4u*Liv_}l7<&pxqS*3;B`ZS6yn%n4N|R}-z_MDvKku$q6t8=+;dw*}1^JlGpN zXeinbw!aDrcMbbALD;*ms57)Ql0NY3*%r>&7-avg^~ch{3MlRM{X+*=B_2Lo(On0) zAqs7*<+UehH+!o#1_x2F1UBRhu01xU*HLSBMp7kBk~Tu^vSddEYYUWb^osY z?rI?{BrP`PY-(XMCSVn@l52lSpQNLNe{S=2dgj%?D!ifQ#QkcyBRO}uKAB{p8)5Uz zqbK@JF2xQIEptECScm_ky<@fz&(}OJ$?~CRrq(g5P~z7F%XoiBvieL3`)%D_iQqSH zY!0mm+m+0C-ft7Yzh9){;qQ;}oIL2fpG|2%9PVO=HlOvX;o?V3u;*efq3KD5nH(mg zW&dkOv6wEc(OS2Z>F+yQIl!zd0rBJGe8gs0haKAVFjQQ;-S2yR( zPaR}?0rai4QcB3z%zw=+$HC$a?GNfzgYTz(wAbX&;k5a#0 z<+Md>UuG*pBa-}St66TJx9Xamq!Xxdj9!eEfP=(_0dHRmHbLIyOmHspT4^c-^)W z(z~+JQM}{PGR$ckE+$@QSG`alXveHB?%kNkaL7EP4`ru$<%2Zz&@e%6z;)uoGPNpz zKg!R+i(5pJkM!B`xFQpZw*I%vz1?pI3SIgG0~;x)LCqQKncBbx8~;A1oUNKC)k5Zl zDLvNeJLF`8AS*|9r}V;xk%yDnV83Xl&$u!2RyylMlm)aCVeh%0m}(p19i{p$H26dR zCoIk^JM@>kP1#A+J+<|ZhtjP?e4|w0)#7V2SXXTqnkd@nb{2&;jl*R^O_si`C`X%DTG@BT0$UtRoe;ykSPtS!QL_6dO*T$=OA?f&Z9{r+4MQ*J?8 z#f@BbG$O$oevvr(#fzHN*BXI_d@T&6CoMfzEi= zh*HRyxa}?KPNK}jKfbNe0dkf5af8t5>cP_!W4mi=KWvQ}yE~E?r3!5cwX8nf`WL^Y%|GM4HRlsM57ByS*hjiGObv&4vMjp+;xta_30t* z{hnH8(4=uShj19Zm!i>_*$GZ`@d;|YRYBYD zKFhHRkRa6Wp_^LGIdG#3F420WBUxknd3r)pH#_xy?emYi&&`>%#Y|rEv1uu!uHDSM z#w&OS{jL8;yyo5?*?c0sq7HRm`*SZk*V}@L6r`$;L=<)fu3KL~T@xfc+gc;>D3EqW zodlEz4X0)xoXox0ZPY(XOYbi}l7HGnUOj_{RRJ%rxzKdt?E;^)$)%s`xidz!Jn zb!qr-^(TmT7-I-I&)u;Uo^!}zo_X>Gep%pR4r{2wE1pmoMqgbXEwWBrAf>41MDcfy z9r!9o9mvbA&EZK)l}7tNA!P8AkNVtRfI>QLX11Obqf1iM7MzntV-CbJ4y2!|!`hT3 z`gL7eh`A|ZA$hkNKWM*n(NE`m6G2zSy2&d4v7c{P_=tN~S{RB`WGA zz57B+y)X95_P?XGBb)y!gOd6;_iush!18EDTb@MMUeK3O$(iV_-%}DTIgdh-+0Q4o zzWu;SHFo~WTJGd&sTE>^+D&Ldv5y#3zQN2}pJ-jXnw$Bw=0&K>HNz!lNAfMzsKPC) z&SLCJZ|cYDEdB8fs^t!HNxdwSbrww0`)%7~r+B`M>5sL>7A!Ki4BZk%3>H_5^@(M$ zbphYArTp9rThD8#?ZxPR(jDdpQH5&BsK7vtsR%#VeC4wBQP+GUHvGXGt3;5Y>$_C{?b-M59-D##xUPi zNaruvtiv9vpNk_27=gJLW;!1XjJi)I8+b*qUFwQCmXpV}_uB2cbv&sUxl8wz^cyv$B8DdWMH^XOGgp3=zL1y& z3we4?+57ay@2`awp_9k`?pwg;>dV^=;D@@6!mN0r7;uZOiPp-J)MdCrJH0 zyY6GHvi%XNh9MoR?#Ssh%$TLYoil^C5#6Jb>f42&ih648%N3P~qI5koFj77<;uvws zpjD|%4Lu_ltpt@aGHqTh`|j`fBQz_qI_$DHt-}fqQQa*3<}-L(DEPb3ceb5d_QZOO{{$O`e~(Sb3s_md-X z7fhiyY?G$ea@BM&U&`#{xBv|C8QQB{4|U&f_KZ2Q%llBXe4kB>5HVuza;lI-9f+w9_?W)>rVxJdrNmm{!q?ItUeV(3w==6q)H}!gZyPOJri({f7yWF( zJX^d!ioCMsZ&B7m2fBLAE5CIdrYEfwY*-Cn6iBm2&V#AufQK`xJU6-el1D5d~@)wp``H zF>CGDRohqK7`lT38w6E~iZW_p_`H3;dH7L~KNg&c-YS}r>iIJvYX0;n{L|uT-<(yl zsoKoci`d#Dx?+37j5EFl8A0+lM=uS+G?fBREitt`*uQ|aBB-B@zZob%l4kl0+$gwi z#yfevVOPU^ZFim#p4W!@c!Hv*Jtqys`clM!5V`=FvvcAgJ8Zh%b8Uf9tI2H?;NI&8 zj3CG$*q(=9(Zovj59XrpeZb`6_gb^aC~HTQrv6TIcY3|i3|Li}u;*42bcgq|H) zC9rEVh;_wujY#k_43~<^a+9(Dr_H|M%T^63;@R)GsR&3uC zkz1ry&0UQekR>6BW_oVi>q<1I+k!n~6L?e_VkWEeP*ZxS?#}m26!eUVPw;XPQysh` zg{nKKfU}3K?b{&f?@dL08E@-3)L!wFFZteCY38=DKWimX6iID9^-HBk3VIy{S@RdN zOLHXtK=`v3qvK+l&jkE_@w6(l;B6eK;bkqf+XeX!w?KJa;YH~e^j1^8^qM2dj28*T z1O2Oropj{(%w325Xp-k%x3)xv?(d=3e$)mM;^gclxDeP5NiKTiOKku@wA z5zH6m97_yuR5~dq5Couii+fl^5Qil(3m?>>p|12J-Wzfcz8ij@3fcu9_&j5O`xpAT z|61R?G&s<5OU3W_0f6}7n8^33U#DTy=Q*~ICT^BsIVw0SHeS0J{);Wd3*_4_=xBsL z6FYs<$L6rBn1mtA2F^3xmuFh&bYne@&=nw+2y?;KIsc<dw?zHw-D!;WPS$D}~^PL7|Bpi3dL0{7|0a=WMu9h($5 zu8DL9gVm@4P(_zYgHH6Xjn zdhTHXUGwX|G}jkxTyF{smQt$J?x6G=Ky`du0in@*d6o00Lq-OF&E{+6xKvewLSZOg zPaEa#q55|&Df`wy97h%pt_mC!r3R1LD4FC0j}_wiL#6#&F}|uVYo^xc+#t7iD<~bq zh4t^%X9ygQoFryHH?<1m`^?MMZS_W9#zR&qWcoBv6Y0fWy_6EOQ1ofh-h1C&o>pj) z)be)Y@Oh8e3-S(Q-+$``9~D=pT?^T;jo>VkZ$ao)-GnL~cJ3U|`F3;YYH_?;kgSoC zbM4Uh>u(jD^VM*(8}#u17{ccLZ{2^XRM08vEOu~C*G|N2osC?iAK{rzz8$InIgOJ; zV$8uPx^PoK`(>x}#L!shg~MUnuKr#2yjc6u2sY{C&bxuM!Nq(;JLoB==Xtz5Zs$3! z{<)KS(BWRSCQEO0(A87JJIdfLG8gT={?uoVHI^Tp})QT23XmW z-4#ElVD%EXSwyN)bl=xBoC$f)FCELRz`3t!F!@4gRc6FVZJe*-13p90Cq{1M*Di z6P($TO1ywxpVc*CK95%qW*hsgk}x7OyU~$Skv`AXX6$$h@(oksd%i#M1p$PLwlG{g zJX$PzK&7lHaPcreVgKIt&cQ`KD36wyWw|wvZTLEPrDtibwftaKK%%)5?4NUMs>c88%uglFc1V&5@^RU+{z_8t#AL-(RK;UC4eil z{`~`E7WYsqX4u@kqXiUpE$}cJ;z9T>RNyv3%lceWpBiBt4)m=fQUvJJtje#qwf} z6+t;|hd3qT|IqXvj%>Hz|9@1`-d0O%G)P-Y)gHAaS}j#2ZQU(u#2yu!S|L;kwQJW1 zRaJLe#NMNbJ!*#7v1jarkYD=#eLw%g^*ZNzUFV$V^KpnjAdB972Ztf1{EpI4!emz! zqm_3=q~!evsWoq-Z*+hOqmUh;xx~}Uu(3;@g*yvo55_0YJfMQ7Qgq^jk_%2{0oDA9{T-a8XgNqdWI6I5Mi%|(8S%IWxKI%FfY3US`ukM-c_{YG39;(5}g~ z_aXg!&!InQEhX;aPj$ZT>J#zCMB`?oYZ%)p@wehG^?hq<8X$o{ub=D+otYrqTxu1? zPmvCI?sX6O^2B8JWGvopvh@ip*7~1^lj%*o{4t40(#1|!TxjWPR32$#5?Tbte|FOKg6+vO{*h3 zMi#3_xcLKQ12_&=*o$^$vtlbFX$<%PqaT=kuQ>c8Qi+A{iEROh4O~aRs5``XJ>*2! z|EimH*}|;mL46nBAI+EJvK86h=@hUZCX1E9tp9^7!!nWzi+>*V>HW<13rJq)=*e#!;SilrwXS@ze#30%O+EE& z2S(WiQFp(bi98Rb9FD_4*)zrap3Vc+v1Y2=_=hBJjQzU#2G36Na!xh0f%{I?gm61y5-=R z&HL%^Nsdg6gI8(R!*r6u@<03G;2N4!95)nC-?Mq&BW{D1qFM))7 zvR9FMC7)H}al;Pl&hvz(id*I6HxOU!AEvYuz^bP-mPrk~{Tt#W5?oZFm8zyVHrPW8 zDEWJ|LV5asq2w%>95FkI!p(M1Cu9ULUqXR;Wq9M7Kd9e8*AnmL1CI&!ot2nz*c_}G=7 zWHi-I##wg6v49r)Ga2{q7=p8{%n6ChMv1~~M+&E7pH$}KPJhcJs-9z7Qkkww*Rz7S zKTqpw7Bp@8|IgFch3Bp*s!ZG}`4V72ig6JsRkZi+(7WktJ;7A>{N@i2*UE!mY_g7& zG!)`=D(B&e<%{Qr%0{Y=*&dV&dc)C^l0*+V&*tw7i32@o<3i$1JHaZ?& z09tPg|IJPHy|AYnD(*Y-7Kdo0EVV~+ig2M%r&hx0n@b!k=m8ab{#ORvBO}Em!kMZg zuZCjNluB|Gy;;X3ikoKkIGo~Og!gOodqcu&_X^N@5ZslhMw#Kbyn(E$TIcVL>kvHd0w5P`@LsX?CNXJ;rz zzxa9jt5i%)zOsz%{y(WzjkEvCWZRjRL14As=6vg;>L$u zX+o$if(9O<>m#Drvm5CYo>>0m>Plp^9|mLg7GwS>+rP}-ze~0*_sf^^9Z40A`Q>+K z+1%0pY>SFn*uGFq`@viz?@-To1}Za1P34iN>ED9t5(E3`g1fd^UupUY-YoX7fH}Lq zMk`&OKrIV1VzCzdLybe$@74x_?t*X2(3+wjF@`QCI>pxo|uc z;IX^DQ7m!31Ut7KY-M5wpPNqY+VlIVEM|pRvQ_vPIyLmUek?Zq2|KCaI=Lc<${x!2 z1azvy`7YC?ESHr3Gr}Zi4h$?}j=6!a*75^rxMa=kFZN;S;|7S3y@WV%@L?g86$N2? zhlq_~JMWCc9(tSuQLW7riv4WkKvgi<|2SZ#%(saDLgUG3v1B%4ud6TaJt-=lbc>r^ zP(a9oqWqdf!>YvG=SkaoOcP=9{|NMMo22RI9jqqGyPUqWm|;WN)QXww;lyEqK);ca z|8vR&W|UB;gb&M{_h#_VNK)b`9kpS^^L;Hk`k8B*1r5)3lXosEd`si8mXAV{Z=)(< z7{A!vrNTs`bR}EW$90`$JY~{lHT&fQb_4c@opjh9^`tzk9hKxwQ3#*BBDBTVw{do3)Nn?5&;z%KG zQV0Y&pgYSqoyn2je~h%SdWkey);JqYW{(S%N0t1)P%GQsl4~;9!k!^A%0aGM!JI+K zH8dwkRL~G@a`7XOtO(@Pj^}E%|6~2?I*?7QQEI1>CbFtYUb9`g+?!$8szTpt?1oghLEk zdbWU?dEoSU4U6~VMfzoQ^IU0m8c1QP*I{H(cm62;o*jjh!+0TZ|C3@EeGOwbgXa3_Wfl$OzhTyE2yt2y&uM7LGFofWg0+YGkU?tgpXy#ukO#zteGXLq5!4 zoHj!#{kjGOHU|1SGNaXgsuI4WOg>k9`*;@7BEOT^;)k3#{HNagXv_bfo#@~#$9WHv zO_&QO^&iejptZ}~i2{{4KG`ls2oCb?ar`(Dw@_1)Zlqs#Z-4}43EVGYB22SfQ0dn+oHR6mzp zO~`>}6no&RW-H8^ax&hi!iUAJc3oDb$Vn+(BH2Pu#;!%Po&TAy-Ejnx)g`vYfao*N zxtr{b&}P$G(!q%oODA0*!UDz)h`xM9(*)vlnY|=sqrpj`!u)7d7cSAVXk+Reed~2> z+#+UPPGG~OXe`?O_pmvQNmlLszsdJVHchH5{BF;+1!0if1+i^3o+qBBr28!G^P3%??6;UYk?O!&s`)?i$3G-6!R3@6o_O~t;CO2r>DkTBOE z)CH;YiUozu>2n#|qp#QZ-Z+8QPc zy6o>`xP2$@!2FX<8A)2?J@QLo;C)CR0Z8d(MvqL?cP5iH#EWjpN(V)@@c0?7Y#(g^8hJW>p7?gq znJo8iY^8z_iorFZt|S~FT6A|%RH|*j|4!C_CvTRZz>UJjvxB^C+33dmO!SMZJYr+q z-m4%wqnPQAzdei3o^qb8Wq=YMUqOEYjyphb0{uHZT!Hu%Q`Rybr}%pBldv`*y11>- zp3hgOjwIv4r6did;V?+IgN7Ev8o<~+x#MW|27ZgH=8VfS`TN+yLlK`D28*B3VFfDL z3u_>yl5?h)xDs~ITWHwnu|zDN3vzC85YxV!cgj_q{kF;7?%36d)Y7`;Jl{Zjl?W)6 zoio|Q3aHC|Xmp!jD5;TtxBiaUJb#pkN##ntfpp%fXc9oNSeTCVH{LI6JmLi@4{O63 z)lNf)6H;I8VNP0x`zdY`P?oqG7jXZY9emsCtsY#R*waarkrPRvYrGB>r+60cVfvtF zBT$K;^A%(et_|%1!V}vDhR=?(k$yNLbmnw>UgC7#G23axD!Zv!eb|E!vCF2fN-D0{ zb@Q#8+U~&4`j`aV#5J93xXpV6=u)Rn>ezg;lp3o~`&GkzqG$HE6So-Q9SujvfX3qm z<_% zR_o!nn=H~;<4ej#;lJR5t`34t8a?JIB?+eRGzNAMAcN~nH&v3IdEq>ei8}!8?_tEl zZCKS2M$;ZE3PhLU+A8><=^ zPpv_p*0jf+;o_XfKIuhh(lxuDD-(CGt!u4(Td&}`mRcWgGN$~B>~|BT<1sH1QZPA- zEEzalx|X9((y~xJf;ac-z5QLiM(EJX1|zT{uNko^PR*)!1q76yJ=cXFg#CIK{l|3P z`~R*2uq&W9bI#LDY0VQ8oO*Up@hrt(@nObr8oSMX)Muw;LczKXWDS#+T=nYgZPR}P z7qnS!WJtK;OC9$=bSCzlpD~Rq?o{Tu8xeIaN$txt%ZriPNee2xi4?xMK=y34^C;v8 z;36cmO{f!dGxQrUSvCB*|4m>^KG!iC?dRcnOCE|Sy*s%m)#}HJf%4RWyAxjFM+{#F zerq_mrjLBZl1RN1P@z2)tejnKD>di~X;bI!R&lM^l=;J_<;7*1c&`pdp}X`Sbm-SxcY) z5r)1iCZ^EbbJo2w+T)Q${AY_B2H-&F<+kdp{GPwHhYrlHdsrdW)VgzyB}7=y@d>La@1;v}5PbXz^=p|m;TfjT#%$$`!iFiiDMd&yA*^%n5 z20g+w@w3n^05cjJb5mQcSbuftfxpz^dwWU4gvsH?A$=KHj&ZRlgvi6RpM83XB`)(P zQ|(a=_r zqP5cNMfsOrLCA_x!b}SBt*K)WrsXnAHahrCHOwv>0LG;{HbaLSZ`}0GRd2~fRygwo zNLBTH39O?oPW!O#u+2IBQomlhT0te)7+sry;SjL`CR96e0zQs)_9FGQDj*Xa_|E76 zllQ{lvnTTmU#HUx=cKLzn4|Yw@q?sSK(ArLii)P|r_rfLBY6t!bZ|BJru`?@dIRbA|Q2^x42p>^W(C-;8&@5JmI_TMw*^aaAHwYS};oSaI%nXg;z+~Bh+2QlO zTR>G-;9JITW1W2{(~&<+JnA#X8#GMMUp?){c^fBm(~*<(B%`EN@B$~$$U8B&>M0Y% z^Kfw5(GA`f(jSCDS^mby~ z(~oSrp)HMj$;IN0l+j`q&84UhRqbGCL+3=wVTM7*$N*-e>g4-Lq2gmhkmt#o*kOl9 zZzk^9sz`W0*6t#4aX`3LTM2YdM4?!-cWj9%swZ#?du7%6L>i7#GlRGcoefR&?NaN7 z@x4tHs;#OhSg1Jq&=(R0W0i&}8F>sYUTH371$b301c-kJV{Hu7u=*}zoa!5w(QicU z^*x&g=RBc^IeHL}srA6}PMx@^A>}I!K1kP_e477%<=)xZ@oZDm+4gD3x5nkgzILbd z41a4pw;^6MXtXXp&?=UW@@u*5KS@!jzxcWSe#F2|B@A*lSyHlVEHUe_3}{X~Z=xPk z@g)xXAQGPvE{JQiaq1YG9vYPjLpZu0RSZXQZsmovqVS3*_=RwA1{jjMm_<))kJ^%$ ztBlVE2ionW|7#6@)l`4ELOI2F(_|j^uu40s_0AfWs2hy0RJ;5C=9;fzcf0IQaWjIQH_5` zbILu|kL7*S%yi#UzXw%yj_EpqJzE5{YXh)(-w-So(+4q7U%pE}^A(O_@c+LS({8NJ zndNkn+kD$8k)!~{h%1RpzUM?Ya;ibAtrOKa%l@^nD z__?7Dixo0pxC7ipO>|$VIgzElTsxISq(cjZ_xGmuLe|okOEhP)llD})^V zG2&w221K2(_4dG2KEXs&QHW^~!C?a2KqI){coStF1KIu7SD|o$lTSMp`hG*{%nFmd zaRGP9xP`>s(ELAnuN6+aMr!F_ABgI}XdDNIZnC%suewp8|QHobWf^613~ z{!&d88|`13&BrF{ni*t8Qst=kY%yJEv@+$hQJRwec5%ZFvBwa>)Qr;3YL3-b?RYp# zzRdVjS>Av0>9BUHsbiZ3TcY4G3wX4h$>Q8<)3+cz&hh(Yr>7_r!;lU8@n1azXHZQ; z3~r{YP7Pt*kFU{%^(Yg?5vX|dCd#*n}{juyGlIxxVkV~6O zhE|+>X|G7_cz7wUJ*&!RIx}97zD&79hpcd4ZA1KB%N-Hl1H)0ehJT2!CmiMM;^5K6 zFd8PFM_0{O3Z^c7|9i~jL^T@@ zDH0bHUMjo_x)aRi*_Fl%^S>^s>8m?FNi%2%v0?Fg;S4vjp{C*|;z#fU;vhransul|AU2<-5cwrTj`^eblDRme@$eP- zD4{^VA`czRZ*9bQA~5_w_}|aq`=vGW0XY|jXR_X?RxWY&0XGqPfXD%D$O3L^o%pH#8_kiI^QWdiYXeZ<^!HcH-|Y4gN3Bp)1#%zWI^U$5=D5~_l+s3mKZ;X>(1YV*Umr1%Gd z1+f+gp)KDQ1;iaaCwQwvFg(Wm{ksjy56yZp!|^5x z_CdVfY5!3cM~Eh%c^IOBZvD-##}49qPRf68O|TYjM7@u06|DK$@n(QQ1wZFScnmgk zehJnx(6&wsr9o=d0NF7&hRWrNYFEWjT;)vkICoansoMXI?k1}kvW)A-A5T5D`9{vc zemMRXl29Hf?f+ge>MSEhj?1g^MwBW1RpoS0tZQ^Xr4mTc+BEI#jH&W7qb!!u(S}bB z9=2C64m07dsR_B=)qtrlU#TQr$Z=lPD86uT(YdV)g`G`$X6C(WXy%)4Fx2^)?i}Dg z*_y~+8GG_C8yXvUX<5SCftEls`gZjB19WqNI{~vLw}uPm36(K1x{!dQa;K83A2tB6 zXF&&jKbD8IWJd@Gg@=5&YFF2tOLPO3XuqI&W|$F$*1TOJ#vu1@LxOUNYfz*=>B~ao zJB|7H-=>!4#20j~XR1CCu5g*g#L94DX^UZa-?iu5*r=_F&p!>@FdN$F$RGTH8zSKa z?EAsmPOjAbSvvE849&1upZx!+NP4%rte~n1Rmu%PCx?J$y;7F6#b8B^85W%l>*xZw zND28)czb= zpS^cpA3RoR#{c6`fQ0u!)s~@kGRAu}f`^#dnJvU|Yp!NDV!jKt627e}NgD+5WuF(S z!oCtjGpo+XV1UYczzzou{bl{@KIK9z^YlmpV_w-zlL|!&NwI%$T+UaWU4KecMD8m_ zLC%IDW|Xbj^Q~pNLGo_gmPif^-Xu+)K%Y&tLn&?0a`Yc{6^hFIKAK7)jzM-T03;D6 z4;bU3mB9em_qw#p4NP+^XWiEyP@9R8S4<`w1#D^$1QfPXWvP$=SFRuUSNv~_rgw(x z+WB2V(kh?bH1)`P>ZbFg@MiwU)JGq`e7v@FC(f_iK#1a%jJo^c>!;g~53gK)yq16M z`^CGjB7fYz%97e2%p5ASU4n_L8{P>TCoP{eHFTu^Y?{W~gT~GFGiy3%dVTBI`*ow| z5LSO`Ml<8ejn*J~X|rbQA_o!IoBX~*j|#Nyjow3F+GSFtRm%qX<4t%Uf$dtdA@`V;YC7 zCH;|^caoro@=m%NUCx-iu4s;BUv+nTv1F-YzO?w187L)ikD!Yb|CSU})#wkO)H5yj zaTA8B!o?1w@LpB>VONXSH#cN7Ri6VL?48@4@rg5EskAe7k(+_jp1D+e<((t zZ7luQOv9UxQBtXU9ZMMu6nU9Fl0N8R%83)`E8Q+VzI@CDTN zbN0wA=U_7E9_%uedS|l&JvQRTUTl1k&Go=sE;X*`wlG|Zet16J%}$2}V1Ugx)5X1N zqA%}ZXY%EC<(j-3kmp-AazBa6Z1g%i+3K+Y8OkaQ;OLJ zh5BTlze&+OV0IU$O2Fe9dlOiXNV!@$O&6psmC~O#9#s@4Kj@vaL-o(u%gRo4g$psb{h`Z%<$;Ru%2!R~J`@g0n)(({-2L~UAU+ne0g#~SV>HYdxwPyTjow9DOKIOj+ zEp|VVHwR@B-Goo)H&b`r{aZ!jwlO&vNk`x1TH=XIz_6N%Jf`HqFC=$-nrSduq3%tJZ5=QO{nz}g1*9TcQP zw{SN_giQFXtsFl8qUJIb$Q{oro7fMk}6|*{8;wShi|zBI+&j<2t~58V+g#L zCmGtR;*F!k6s4}UoDU>C$_emR;V>+sKIOe8#MFAYEEBXwuq0yvb}tJ>F05mUHtq|c z0)MW*8wwIuS=A_Y@A6IQLD*pLY+U>z##f+t8Oeb^c`_~JHIgUJUoU1mAsO^-v{JlG z;}4VQFnbrgc_KP!a|Kd54#`dR*(N+tb3+saZP~zN%|P)4n7-ui$xswTXmR2i$HF{*e2LU_;qQungzEx-hk~1$C$@(5+(uvJhklCXKmGI#7?1G&J}R}ikREd%j~t2 zKOY-^HHw%EEvw!u;g9O>03TsWaU~^#hUE7G_G#i@NZ{^*%Elkuh;=P(buKZuU=({a z-THhhG-{XOLH6Xz5QP04<7=Q8bSsWkkvVDOnHi3{?bBp3bH#TcV8+0OsRUy=|Mk~N z2;6XeK~r!zXrs4wu!~QXd%c%wz`uJqNY?PdCZY4?H?WmHK7^#doX+G`{l0Ubwex{U z(ANlOeD2dis^>4kL07=H2;c0@=hpGjx{00AcjM_lua_Q38uh$kT(&X3>uSWS0!~lA z^klHhZ|}Ra!zq=$*vUnNh)!K2X zSkTCk$CG&v;(8xq7Z@8F&Z)Iq6U56w#C7iq2U&lm zxX`)El}Mk|15V0q|GfuBxhg*>o6U6%9Mr+xNjWlGowJ*aZ1hr)DbIMES&1QkVd00> zym_GZ9Yp|SsRYB_I#&}M(T%qTzU4r*xkG4wvB#U%8k^F0SF73n6CFW)v|_Fk+Ikar zklP)Z;C;u*#Qd9PVz#@@S2wOM;##-Kofpq+M}=K7IWhaeMwsHGQ$5UVeTPDX)-x*lt#VF6r{Tp`)LzUudQ%&F$f-KX_4LO^r|n^g_;Zai z=2Y2C?@zc(8UzAF{I$3^Eo0gj7Qz96Trwv`QI+0;Kc=5~*4YA$r7>Na!9wsC%W%@) zvib84l6soI>CCifyom$MNihp{)drKC`~u3(5+uMphXfF{8o?7ma}aXWzCf{H2m5{NSqXp~ z*9=?-K{J(%ZyqN1J&fjdc`zr!Y^@(~$g*ChCtglD0U2JvJyY5)b>S{j_r-GXHFXJy zx#6=q%795f(OYvUwYWIjnm?p_M-cm22e-6Z*6u*tx>hlCfW?17(t z&PW@R?|WTPoLRN6Pbuy(Zd_jSNad3k_TaSr1P*B(sO3c!v-ui+VwV@wPG{&mdMQQI z&er_XYruz%FC+}m;%G}?x>7T*hE4`i@#AusD=AB=+K0^yeYW&TL z%|FIIkTY7DBO;(y%NHsb)RQ;eURyW%Y46`yaz?!>SFqj z-KD`hYdtRX;1;1!yfWff26TWl5A+El7b6m_Tog|%PXWNFi5gGWq-wK91m<8MCZ>X8 zcFS3y+X;jaoUyyDsTxee)TvjGKpgBjr#smr8hxLI!%dC+Z7_-f-;+tB&DEz$5q_tUjtfntL;a9c$%$O{Zrld zenat&z_7hJ)!g@qW+gd^!FIk(dSjvd?iPmcV+S63m;R)DPU%@7tiN!%CeMZI=H(~! z{C;Rvj}g7;=g6P!PyBPMXtH9{PFF=`PFMHzxVq=JV)f-<0p-enZ0D!`$(X`x@1)yj zi!xUQxh?k_Lz=Wmc-mHuz5<#^$xe*&=;$G$IU+0_9TRjn=+bH~3T#SQ85kS0+wXG? zDx0smi2IkQY~dpDd%cq_T3rge*6i6 zaL`+}>#K)FqOW>CzjVEGDY~_|XMN;~vL|=(x4xHHu;WX#U7>YS>|xv|#v;>hZ@uHtI1rdJ14> zjS?WlGQ1LqS4u3Qhiv39&4%4e_3=4|g=iKF40l>NiEZvV1!e9A2?q6|M2?Jcg+O!( z_3tLIzMb>!ofHu~`M$=sYner>pkR+H`%9*!LmU9H2{|iha6{}RqQyHRM0UvkO1~pd z1Ny%tth}rR-rTHoRVz2#lzty58>yl+L)vjiX{8IX`nj)Sp(cBGTk|pzyn`%c?7wc`0iZsKsl+Cd(NN8YE<%MK6i zdHB=CRiBCAuM~TCki!uvx#k<;=JtWZlstrQEX4Z2PGrGIIO_i^JO%nDB|a@@=( znUyYxIF00T<8tbs@w+tmIKe-906}>s)A%rh9X}TAQ-s)j#3x zmzCk8yz0Kn}nt)i3hOHB@ zySex>16#q7D}ZVwJNhyCcbI4UkqCGyl9qxYEGHG9W;0f@Ytp-FF&vWq zftR$h1@`_#i1gF`7CQOd)E>g>URaH}vu(iO*B`?SWm!Lqf4;jLm-tGg^=IN*;sW8I z4v{_bE;Vy&iIk`}C|b8_^F6k_<)YF%kI|#MMpd)5T5HXkMX`Ej+odnYQPKVrcB!VR z>Rw$HYPJiPy{=kX3cgDUnF>J9Ix3prEE2p!LS1=p*smpR)R%i5D&b<}ycR23WyE|3 zpNOBEX0QBn@f_2 zQKET^e#T?&DSa`chiY&i00y+#*A&m$WeHV?xpkN}*M%x#k$1Bd9nNN%A3lF^*{|%V zB2++ab&`LOlrvd&>zHK;%y0I$eoMj>c*;q8h@gks1|qEE=&VY`Kd|Y)l-$oT;d&_e zLf9w&O{MO)Z^jTWXD7)1+lH$D;M1r!HTALe3FA4U_0z zh66!4_GwGWeYJC~9Pge?2-|LjLO%`q%$I=Si@mqIC&0T_blS1LrF0QHgIh6=xe+As zk|douTxS%-c2!Jl3onBU#|~2%*hsq(8ltGgK9`X$PKxPF)A39|&xPLG6q>5JoVF9L z4B6kWtZTo!!jjc9Gnz*@z?VA`&+zFF$oY^od*A0ZJ0PSay7zCL7M z(uW1i*0eWEFwpQ>X_oP6x9JC}FXSdBRYdbuKX0sEjbP5t)qc%XYim=cR{td9*6Aoa zZ92I^od=!g<{u#Agf7so_3jcmjVHfFM>{^sMeGF^_Lwoo-}3J2HekmOzOAL`neLe9mGmPw zN`I_9s9!t+8}&Hm(PlNG-`2}_m3}_7j`TnisH32v<@%wSQn|Y5zhvkR}9?0s^)tJVil)Zd`8_BysPxOscF~ghK{c-GX}N#9-v7 z`&m1Pg3Rf>j_b?{fbtg+5@XWtU@Zx36i;ab7f6N@bW#t%!2-4_dy?MDfEN`SA*Id^^L)4%Cg(xE+4%2>Si zh8sO5!a2W}5<8!LDi0Bo*m$5Fu7hf`g@O&=;(z^+9!A4kBOkn5e$ZMFDzpg_l6fFI z?~$Hd>EV}rc?Q+%AZU+x)>)o$)I@U|N!bd!aO~#tXR?;wEqU&n(xGlm-0SxzbsGZ} zl!dY3velR?506Q~i68Q>AWJlnkC>?QoYviQgB~wu3Go{*^?AFEA;ax*urrolBEzyp zJDsDvvk|Ht^nBo`IU2iQ!VQ^eUBSO|#2Lzgny!?(s@u(M;;gp3;21zLoq{hlca>Y( z=AQ{kc7zeiYp;PeCO*Oz@Ge^(qUTGgu(#W_$JxvHIHa890OV>a#nq)B79Btpi|T8r zni{6zCJxL$#+UOMwn>ET?!^PW{YHT1zV@A@L#1fxY;WWLxX|f(6x!h!|qW0C+f4E$}~6-Z|sYn)qCP+yI7Aq*ARw19U+| zvK*N0$^Cqa#}l!cQLf3m{s5%B>6nPRmO#MeXpf0_>8xLf)I6%VbbyTge{noqqvI9C z_`>Ui2`cIJ^&X8axDnyFFYjTCLZ=&&mSz7iSU;k!v)(s+&mZ`%nS z_rnmgbOPZ^sGg0IejC@UPbD4&h)fOaBIRm7WXqH>QDCIccnmpMOm25^r*?SUO&r9> z_R1`_-WM>LQ$L%XgNtY07Q-9W4`9yi>0qouDp#6uCr!)sFb6|eFoxi+-sBwn|Fr#}b3 zKF@p+T82y2)0j%_5HP;MELRnR1C$JUc4bJ> zdr)cuAEAkRibqdK^xHb`y{$P_G5`ty+@c@d$t5*Pz6+Nxoh=EjHNc`c`yHu9)%#od;K9Avr zNlmEf6RM0Zy$4G$9AIjsq%SlX!-kOt&v}Q7u$mF`aR4{*zQH+X>_#}vC-tx}D#&B| zH`ybADQM=}0{6LvN!mt>q^yk#9pbodR>I?eb+M3{S0Q&Q?gnyl} z#lD5px0NudI^fe>Pb}gq=P0Q(sXw)VQd&7S4 ze{Olm1WM*pT7TYOU7N1JqFotPz-m#6Ub|le8^k!)R7y3BRf&Vrww?=*?{@7?@-Q6Q zr}ZkGORKLvTP7{q8uu6dRW3J@|ITIi9#<7)`~Np3FefX$W9g~Tna;~eoLmxHVlodd z=j2@#2jgr$X{X?saZAkZ^&X(&$u&1{J*kPb;=O2r>px@s(luzTG zedMh9_hJ&98SUQ0&ZxN%Y-kxMzg66 z+`OrJwEEMjZ&2nUGdD-yRFt7@}goNvfY=8 z?kvL5gw-#5bD``Ch|!qKpHYXtPofwyFMrmVX2`0#ICyr>36+jGEf61J0AHBFPoU2`X<<`yrV*!YF4hFY*43Tcy_hRfJ07Y9~@=(&% zu|u{*gY%oxr!oA2^dgc;`SVPe!}WiFfNt9I>&oEO&*JnO(V2%I!rog)J^LNRSxoQ2 z{6u-(o>LnlqcHX$JqFOhXXacOrAKu8dLY_(sqIU@(Sy{WhY-!w#yR1KOn27Z2hBQ@Jy%p@CsB%^g`~pq#z$U*;S6(hc z6fv2V?X5qo0(&Q6rdso2{xpYQ5zFNtLPF2q)UP#t*KobzrlS}svF2wP?@V8II# z7HmkVV3Ak8>zPj1ZK2pYta$U}nb%x)SPsEP=n=Z@PA|;P3PfH1^fU1BP$uFC?h*U zImpO%tRh*-ospGsj2z?G<5O$IibKVPT@ z<1BD$N*vUEgc!O8slKmtbp;#&_Nyad`e&b_><>PhQQ2F-pmEPVfUUU;<8{ZaUTJM+-OqD=?aMD)w4a$>v0Urj z#T6^(+kVs!B~9y`P!#JMk^VV%3OQo!SX60!#ZVF=%ef||CV~B^fPr5 zu-}DWW8|Q!NC^eZg>w7kC4bG==H~lG{)CxryPZ@aN`Md_0oBKtSN4IHLo-L8vBHh| z)0+QTcW7R!esiy#rwYp7*>ZnPedNN;Ush(p+}>-?XwoXoR5@OlM>Jtw3m^t__=TIc~Uo>$r3_5SP-_^ z$nT_}T^UHCxx>NU^%g**GsH>dG>Apw42zL0rbz@)CJY8pe@t)+&b@%z#m?MNwXZJM=X`w2z7go(` zM<%!TABeSVROfdI;8jr(x1zxZdmdv}gc&;H6Go}iz%KFV;A@^nx>up3XdY`xh2o>} zY6ivrb}(nq|GtV)NwP6eHVY@U2C7abLFRd}?5Fdw)FV+x_Qc1+nx?I_)-U8Pa5{4} z)WdHhH}%wVzltQ!q(6!5&FD=-&WH-wGY9tRoMibQJZWCmS27-dJYt(6 z%9~Vn3gE~y+<ieNom9m{6PMV8d8+stojQOk zwGnh$SM4=Hef*C0eOom7$D14X{+)26e}>yKzM^KSe#|haYIyi{I-qgq%@gTu2ev)= zZ`4Xay;W>xA$|CiwoZGadAgWs0{cc|wuQG2)3)dZObR}wDPTR^Rl*{FDZXjA#!UT} z`13~lyO5I|XL`3ADw4d~D6U?dx`lvK4@4f`kBY25NG}q}4@iyJGBnaq_%&b%{CsMv zp^zR4EvH?u7TjT)mUhK|nXesFeTs8uIIR_%6`Ls=b@mhDccb=?ZrZ6 zz|piY-%RM#C#Ztr+->BnQdux5uhmQ(GfMN&57ho|;=f)IerwV?8=UznQk9ZHiXI0E zNGp?igG@heAMBe9Xw2F#OWW++lhC3{j_KFl?0Nva6#dN+D+-p@D1;FUyp~4w;1l`+ z^Kkg=zzgbF1_!8)pE)J=Ktw-6-O8-F#hCs;CxaBj>9pDV3?Qk_6zpB`65cb+-p9Zu z36*52Rf&J7N?K)KSoqJnrW!Io8$%<#j;&nYYGaJG2aT#Np`P$L+{6Ok;@q5a@RY6y zk*{3zg`5oFY;}O#%?e)-GSJQRu8K37r`7=l!X8B#(6q^+HVidUqlF@NXJp6?zMgRe zc2;K87tTBWDNgPYx-50j<6z>}%UszJX(8YMth67=B99-@z#euq%W!>NCCe`rv*wvE zc4iLSwe%YN9ujEu#3ARtT;BWHNU40q{$Ck6{a8AQmm*fto4|83rD0U;axT|3t%Di4qvT+4 z|39x(rIlyIMK^9T2?X-cfW?=KA%|Uvp$5OrG!GS+|Ks<$xHqOs+l3%8Oo{jE1AWu< zGGOpXipz?8G{#3!2^ca%6>4i*Pw(#;_pxJt$k_U%f)%T9@s5{r#pFxuV0R&5WE}OW zn(PTf(*qnKw8HhqrY3d&Pao{8EawdN9hzw}RZy zZ`pIa9;!7^P_bc^W>H(S5I6EWfL)EvN(~)XM=}0qgtIpO(f$&ypOJ{a>o?b<2#J^y zFPn#`XB)gwvTOHDUCZNwXy&-Qv9a~A3G@2Z=lvj;H+jC~{h)zU)}<^MnUqft8fnkk zU)0(uBBY2ZQ*^mCNG2^2x0cloTd70J6=?>)LXnnzJvN=wEq)P+jR^hrgWD|Ltm^0D z_G6&VWGc&IkntgC+S^)e8v@U0dfPnZ`y%@HoS;*?4`1b#o!mu23gk#N<2>>1(YRs` zhI;Rd@$JV6d_nHK!F-`O$F{YG>Tk|0`#~4x)Kq&wPH6k#38l}{<9*Lo{yw`cm8lup z(THKH(BQYZ?_6J|O8AL7ux+aOKOS(&ckNf1`zNi+;XIy6f}d9+%##v}ineaMoKS5; ztzH5KZn`TRe*NB~Fe3qe9`f0o?;m-*{FcP-)TTU@67@Z`k3(H$c9HO%Ig6|{%8@Iw2CuvalYbKCS~tO`vquE z3UehDSHWP$DVpS_#LiuimQwynwTPd0i)Xf}%(iZ<@1*1p`qg6^PL~%w_SByjOw|Q@uhI5~tXSt4^29lsdqagqGpb2;ZYoCij0e6F zJJ2pI3qNB;sWL-($l(#zXapC$VDTtBxpzmcTJX8sBO6GL$MyPgqs74?ZxkV}YfhN6*JQF(e#~{H{-JH?{6Qh6AEh_#LH~1Rv%M(oX zwL%h)%xbyiATcz+e(`+2^y;bJRmFX~CW36RKhSI`lt@MrkUs?tNRU3p`$4;>3Z{AK zc4=wG{S@64Ee*1Z^8q+r{QEn!DB>r=eWZl7*khKV?&4r$qs(zZ{S>W7Mc8Y`#u#8z zS&ppf5OFxS*~FD|-Uq{ZUs??9b}ePSmnzu>Tu#uFJEL(i&v**v#>i_B6Cu^W9wsYB z3Gbomn&la^=$?ZIol=6Cme1kdH;blU#T{*rMo`tA=-**3!Q`PNoly6igIt1K5c}QO zxf7efJC3fG9+)*pX-_~<7X7jwwuc>PTl1(=f#ss`YZ$Q4wc7>(I!+AbqMn;_{V+FU zlS!Q6zviNF;5}xK#6*-*4}<7S67V0_{lap$St>TS$UgPfaw-4MKg12G~TJF9euDDO^8vU-+2(*@rOH?|5cJbyd?qki7VmAk9a@~~Shxazb%MDZs-S(CjIj8v(bs2PAgaAxEvA3*nx#n**5A3KtBz*2)x%&{szY~O04Kx2H7?%H z8D2cbcPi_o2)B5aJhHy0$$u$NM(cO6uu~65xA>Qjbe4M92jadFx=Cx|%-dbwRm-BP zRzHx@gxD&JJE{jFxG|mv=rxTN(Wbv5*C2DB8Mg^-zAdu-Yi}8Yn1dK41m=?amq!K( zHXq$g*)~;H!p2f4)oS!k$MTq>bke9(a`4B5I>f*4HHkI78^>?h>`@5`ioL2lmeGdi zqoi#xg+q!PM_Sw-jYzZUwEm4-Kmlc!%8hhW)@{lLq;h%LZ)=aaVGp)xt2{Z3Jd4$V zEmKXyyk#$z8IT4_OEv0y_w&}@&9qE{0gJ#7*;obGxtBp3{M5!@Bu_<_P)(+y1hmP^!-t5+uq zMYpgu&J~?VaK09GI7^qItMLB8hmpH(i7C#K0qi>s%{=S&C3`oNM)rfdk-%MjR##tp zQA#uSYvEx2-r5hViwJv!qbxR;Tjx@ z_G&B6^ZNzmHydFzrsq$M{CT~r{Oil;!4{}(5O(aCE(g%U6ciwt}FtZzWuyAm25s(%*E)PDN)Xi zO!UpQQ8UEUxKGK;7wEhV@ z`wKqqe-#>!kC(8<{f!xS$S@vmT~IvkEY7?l#=A*dT^^*7Ul~B@q;2mhFJv`tqLyWX zH#{S;$*33XTkAd_oJnqS|7x5=j-Lx(f4Hzib`)7vrlfQXDR@(}gva|-%`PtloMYDi zh$Rx9x*ngn3A*S01oeamUTTj5S*L|zcS=Agm{^EhUq37}DqiLDb*nFKBwp;I34&U# z-gXqx%JfMzQhj3wYhrGSeLm)@D5jF#BJHRI1(n!}N&{B=3~CX&lC%ZlPl^c~W>1)3 z&h_dW9P&J5e}~7Pq)8_UviVx`GF;l-l|@>-uK_F89MN?Qex{l>N)4yxd~K{W7F2rU z6wLXbaD5wwD6YpnargZ_q$kMj8*|r`>drTm;J3M^$lkYstjgn6uP`+r{gND}UzGtI zSO!07fR3w795x4`x_<+$AKUSwDQ6NS%5&~PRZcYj!+PV0H4cKRMQI>l-a7p`nv zUM928_d>i;tPUvNeTS@^(Y#?M_~h@eOt)H(eQ_SH-~bY7CDS3tUz+%Id%sVdiwZ{_ zI42SNi~NtjvFE`*P=+rIIc$&C9&=2s>D23*k~R&2XcNrWCRahZ{>#5WX)uD^YLi6J zT}2F_zCr|bucev;h(mSyRhg$!lU8H(_<@c!mAsacPAiA|?zy>itr9y(;V@6)z)SPK zVmA>{bZ6MoP9LK&U>T}R$if$>TbQrPA*)+_8@Y4WsM0Re)E!)_eqIX|*%mn|NI0E= z=g2WB@thatI#|U6z4O-Dg-8pqLle09uMHpSComyv1-k?!QfkV)RZBPfN`)ti(7&qp z)5={orf9#;7Z)Jf?P`uj&-^X?ZYsEgU++JmYXYL%u@+M?=hZmAtbMH%!5_9MB2YU8 zw&~*6R&0Wl`5p&hbUqTvtR(;QmBdn5fy~2r9AYdYP4MGRasVOBjhG8*U+TpCj@NEK zFVWFK&Zn(!x-#aT2CaTFUS0?}tDMRvh5IdWW+H7j>hI`B=R#dAUH|kh_gnZbj>C^^ z_w!85)ru^G`2g~1f$3$_xLGoRqjXX<*ePuYP@2qZ>ok^0abX)P{C-pf#Se_2a1TEh;qyW(UGZ zBPGC`WByvZbWk`wz|AA{kYB-Ff!PZG!>VyUOMWW&JVw$JTG?(WS$%kj|AnyF0!fDt ziG)VXz(JhNbx;7SusLOW`98(L30bW2Vh;9Fxwpx4G0l?O*yT?57SKm$S%pMAMJnG| z8n?_lzFz3eeI01lXJ?5ZN#*}$x?v%NrKstD*MFT~eTdi%Nl1WyNYK&D<)1wjOd6E% zujE+6_WF1RJ=V)!DYwgvCQH|fd&=OCySFnIh59P{kiOK6b7viC$C1ur1+OC5Dqw32 zcqNl88fy)hZ|#)*u!4%B0*PiYwh-Q-p_v{~*RMh9Kue`F`}9x*xAN>fBk=l{HYBrh z*xmES29D;c##f?)u9U5}-ENwjTEz;JSA2S!Q`4o*BKnCa9p1)u40T0)VRT5qZ@Zmk z0^lfg6X%HL{gwF1NAS!mK{lin6_l`(E! zqTP|lJNs+sg_5r=mwe8k&;v=O)X>iZINnx~1rfJJF)NFhzv&fS&6i}pzJy4HzYu@w zz-IFg-OR0()QX>V{^e z70pdpXNxs)yfmG9EL&F2=AUlcob6nco zLY8itrJQ2#C^DiED{z(UdCTqbQO$pIl_7LFF?yHKlHXjXTQ2+Wk*xfBk1QUhE9)?% zlOhryIge)g*)YWXvJo0UtHXWJ1Za&eSTwL^Mslwas>O-jBkg*3LQ=MRlc!60G~T8P z*`roS=C|`C#d@YA*tz{x_C+~Qk;7Vs0rWNcHCU80vxS!;ml}Qove}7My=ZskWbYHj z9z>VY(gry=In^mTvSgQKg`p*0?eK)_uihYw4Jv!;3cw~+^?thMa_tB8vtJsU1y!(c z)N%yJ@7uSCN^bA>INION-;fXfav`_Vp#S11)iya)ahU4GRVy%`BQv3{-#8;#bzP2$ z>pC1CPdj)T%kpJ;2HRDCU$0w4U<0bQpwUxWQTXU zc$@FP01ob^gW`GtdB|fb66KP}c>2U>i%h~*rNd1y^MWj#Uf|khUHsgw6|G+xxg>@n z9q`OMmBG#$%_0b~t0If<{TLtW?Z8GL=2(~0o^2$jJcu9|Vp1$StpoZX!7Q#yJ=5?$ zdki_prz%Y&4P^#e`w-{M0}q9_>E;^2jHau#xh`>?@mBOPz>9fGCWRT_{i4MxLOJNS z$wKkEJUy*a&cD5i;mh9P`|_h?!?u99gHi zLb&Vf>{<9Ugl460^q>M-Fj9sEETDy?G2K~}km|QkB*^h<7m4uqTS$yV7tqWKRdm59rM1+LA%pmn9u}_)#r-TIk5+wdG`#48a}3{!U!pK>TFqL zloqBN7BsVErEyJa_>SR&E$#lB96u$hx=raI7i1Ftk~FWQUT}x*Y2L^4CgV$!Psbz% zm{N}Bsg5Ih?B=Vbu$A+AJ9ztOWU@zG8H+ruAL3Y3G{*qBxWn>VQ*>HZVoO+=VM^M= zKTGty{ByugwHTJ9)k+z*io7^6g{2?VY=Iy zcIcMV;+EUGzIhp8{|Q0SjlwOj`o;g5Bmj&QF&#hglO3N*wYS6dMGjNdCLB0h!c@|W zbsNoFIbxMbLuJDS3cj_|0o9yz&uowRku@$}z&lg_~R#mqNB@vyRRKHQf5Yt)kA z&fk91B%Ik_321_0o~A@(3e(O^fg#FG~|FF;3mfm+F6zDQVL#aq)bHIlJ$}S z#9iC2a=RvjThb%$8G$Ag41fnD)dUfdUCZilRuwCf`JR+amRgC3FjgrYiztUt2s*PgX07sa6AH0G4_xn@C{t#Uhfw&D-k0JVKD)53o>kQ( zn75<|Zcp%srrF~sx%^$NU{eiceF4y$0rxzM{afvS9d!QtoFqx@!B#k?xKWr)GmN-(fT&Gyy47AmmrEed@<|r#}1j;>} z0&eA7N;q7#$NQCdN;2E7Y}Obr3x~FF^}LEM5wc{7^Sx9Dp4QEL?yC7vcsc3k%rCe# zqg_SjcQ{!y7$ox>GvDqUyx(N?&#SHIQ6?ZWmvAN^ppyMIm^%*GydOtq1*Y3n@{Edk zp_%=aO^^r156K7^Q4Ch|3$uzo|Dnm{|25G4ZC;d{|9?L^-Xh854J^Y41N&-o)MRgM zTxY!~ZwarRQBdpJ;uk$HRsau?v0KMIv zKeQT%UZK;KSL<{a4&6fHiWL{DncV;pJIm(gF40YmH#%4-Mmw2LPTwHDjesatSjhea zum2ouUcAUzn<9{X$Cudhx$xdf<_W&k5B3&fdyOg3r16U*w1Ch<*H7mtsJkVj?)jyL!Onjx_bG-x?1j|?ODc`@=M=49FIiLS#OO`> zAq$V@7XdAF5a}COZiv?M=LvGXKFGoqJPb~dE;5O2LEwbG_t}lW5tKuZ6b{eC1=X8%vj#a~$c6e9^p@p7^YKXs3$; z+T(CF52o)YlwGP7{N5TdLWmtZvOE5SMwS8ZHlx9^EUT^5xq-eqX$Maw$MQ9kky&Jze*1z2g46DArA#lc;nHxb*{Nc;%KAC^@uZ#yK=_I?8jtLL1z(AC~`V zhTpukCn^t_F}`2=5&k?tI9L;lRM>?soGtuC3&yZy3OnP`#e3tI8qK;BKdT!;n&YAX zodqSO+2qkRY@D#O6bLl=fv|C6Sc2kejAVl+S@qcSv*jC<`21XTUrQARrE;5*HoWG3 zM?p3rq0hPPa=Wed;3Ox3QgSd;u#i+v`;KS1rwOO2z}by*5&YCh1Z7jQTV&@}R*GJh z2VIBmteexg8vOq35R_@I(qk|aX^FovN?jpI%S1cyR#8f`tfEUjP+MCN$J^8Zx#{rt zya8ynYFKgc65A?shJJ@*lJg??TjpFYGW4j$>YR0>E-u-ut|1y&Dt`F{KK`P_jE{5z z`|yCOp!9w-*?#~W2wig875n+;4Hrl=+}Ks~2djVrL`pojl&zTVX=mtcu<^x|i$%}M z*H(`Mk@_lU8TP&CTeYNfkw0^DpYH=nr<7H}ZjSi)4UX*N&qpN6bs{aKR|+!m&cfp< zN_5c|SyfIJ#0O zqBlO2$>xWx+*v8-o1$3q{va&d$s*S?VSqIg z6R{Uf)kp{C&m^DotM6nXuq)Q*W4rFb1I(W)hoS1Nqdmv}8Ttz3=^ZYbzKIhz#~Bh0 zCQHx>*&?Tho&4CQy)3w9?0$^JJM9w^;7UJbX}W(Z{A)pPE!OFLZ|U;}YEd;qv;g ztOD^pd}yX0f`1mCTXSyx!t`|JTe7#BvE|*7i9%sFqY_#elpRcLq(`|)z zf8AIKK82ru9zG@978}lSPlqco{{!xBVOpG7SN%!oE;o`9CEA96ZOba~oEr87fS%)R z?!+3+ys=dgHD>KIzIbP4gPFs2f@kD{4s;q{c{Vn_On@q7+h#cEtE z0pbr^=G}$u$N-+~5ucHqnMhpw*LEclc@L#$j~$9xo>y2am2kZ8>Fv}&t;W|_Oh5i! zCXz=GI(72*Zua0@R>M5!Wc%~oFk6&MN>Iq%?h}g|_opNF&q24+z;p@UHQ9-jDUte( z=9nrvDGav@5Hc5%m~zpP0I^2KbB`SV&=%--u1M9?w}8!A4`l+FgPG3>B|4sKk>e0^ z%ck3_{b?DQuC2VTYo40iZ6~=9tV9TVjmb_pHJ9*uBaUvhK6e5cMc-9kC^qsrC!YH z?7!xb?d3)RVhEkqI`sZ^OGM38Vs183$i?qCb*YE#QuCu?M^QnKG#|Moiz@WW|{dPYEF8P|(|BJa^{lae(2v;?vW z88qU7Eae^}RSR%iPc+R;r=D`R%8uv)fVov<~ryeoclE-vjT|#rUVKguC`tJr(i?PgSJvwH)+Oz2TD> zzQ6Mx#zj={B-(i%%)Iqn&-Ru6uqxjU6N#Lr%mdrU`Lgc z1{i0mOsfB|17LPZVV1-9=O`Gb!4}-NHe>*XTv2~Xy@k?Y#lwN9F9l+w>7HK>lR1Qb zfh6hBsYFwIR;7LpDp#}jBnYlz;!^sOgK1yq%LH{)QZZC%u3&&)|D(|j$@9^Wf2y1r zhvELoYPMnV2D1?qY+mn?y3c;jO;BIFXISWt%JKZ+?9W`{#^y6w7*aD~-8@mK(|0_= z4O)*^F@G<*IBJEMUK8!UI4<<#LHHd~g27e&*UVpbA>QY535#CTFvCy?z214oXkmCu zo7KV+qK41_7}=^?ai+H2f5MCG^93xwdeQ{aKmW5+hN7oVn07rC0pz@Idr+=7AF%yn z|0ed++P@WuDV%1gUUtmi^D3FjZy|DAsB3bz=3Y0!@4s5O_^9G?N(~XVs`C?ur0u`+ zLEBGfxw!s+1qo5?mu9One}@yE!*F55PlX`oKI`mze394sW|&wlvT^X$cXY*Dbz(1yJ17r%eze>|Tu{t$4UJr=so#nE%S1bgMXQt#} z<_gE}sCzYSSP)53O=~<-m#v!Mu8^an3P|Z)imPq(d|M7Z}<85tL^wdhs_%!(KIyv-Us46eyN5?AvE_Y zG|g)@npRx3e7oVM1CdoirEmUBnFUBuo}-1 zZv98jFVxLN34HH%EK%U^u5E->tb5r$8CPo)i*-UqtfF|lh|_5wPcEZnTq)gsv}Ujm z&a)MBxZfRm>PHasP~73JXa6GB6?n-GOAUEIVj*#(q^IgLXxGIav%@CefOpT_#C~)c zUoM}9O{~X>lt_{loet9Xxa=dei*rw^(`O$YuIkUrgKFT+AuIc(X*^><6-xa zPty7xg2N$XgEq=Xov=3&FqN6v z$Li@}g4|6FFHEUR7pe& zwTi2roSjhmSkI|ROZw5l^v`QMcW&GC?eiwD;SQAaH`)jX`k93dVj=KjlTl&dMb&VKz=ZekCF{vbxX(+x!LwCl zXryPIHfG^@Z6kN@DMNXOo3L9xs*oiomiey6XJ1@m(9CIe8tT*GGE5OsoTE^Vhbxrz#!k*H@rpgZc8#!{GMdrl6 z5@1?}WKKnaKo)+%e;mypu2u_X(#YJNh_b+$pV4w>;yx1VvRn#-cUI=_)6%mYzfHG> zTnrISjWW}LE@NFb`kelw)WsGfEV!5Q6&Mi^l=-S2mmRdx4-;$cy8Lyzc1$qei)pav z?DV7E{|!iZP_YS{%!4j0KdEn@q1bVw5WoPu>TJv5L67EGV=ojdxKSbi>AUxhs`hX! zUebKAuwfqVOjuR#{($%-A_JHm5Cu7XJaDl0ES<`gJ8j{4oyX(yICk<=CB?aaZ+*7q zVIJErZ>opMjpm0EoScl*XXysX$mo?;JGS|C(=(K{X9H@=FU_phEGqm?9^+=MFmKNz zdm)^p>E>+OD&!|euJ6xni-1~StzA;iy{ zICp50!)Q0Mi6|A54JvI#hR#NwmxT|Myl(BfOU3rHHF@KHbJJ)H> z{T{VtIeB(RT5UGtk(gzNcP#z(aqDEvxZmdoqqz;QLoJ8s+LqTp|9rm9F7C!{9#0J@ zf6H)Q-59M4AgC$Ewugs98J{8PO^W3$_ww14Vf~NW`Yoq=1L~j_LjheeQqQd{BT@Cp z-)RWZ|CH#N8lFeDM$_^0pIIM=M!ib%u6$}*qF1i|I&xN{X!wJn)lkix!JoWK-LYW1 zGC>?6aWl2l*`@>sd`$pculv@AT`Gci~AU0;jvenKMy*w zAUMU!#wsJ3msJ%V@%B6mr3mW17J2pT{{4}yIikklTawWCT5f9o=ARYKC(q4W0y?9@ zDs`u<-gFqYG|jP^5qJ)T?zmsQrLCUe7U*O!CKswVrjR{b-y%PWr+vZkJ9pn!1#?*X z(4mPqS4qdOTK{9~fmx@K?Uh^VbmR+*e#bCR__Hmw?eXJV|7IUMVUE;3n3&B02J^gZ zj?exK2?mqA1@ta@Uu62$_!YMYswV1?_Ou}6~}b$xiRCRhO6mp zxrEe9WK#b~cFp^d9o!t%as`AXFpnmvf2-ec*aL|8&v@t~)Ud*LJ#(EG_7i+lI+7G> z!+)#(QnBWdZI3Ye)&e02z^D_O#v*0_!`?Q0X+e8*a_)@D_Z^<`Z%l-X9Q(DBvGwr} z<>kRrBS82sI%*nn(DeMxkXsxFg8kDzSDzaYz>NRykg+T8b4=%P1Br}`x9$iU?#OHhIY-f||!b{hH^ox1$Ieb*y!^8Rjz zr+LOC@`9xtnnD?JO4ww~ofbuDyulZGfEws)f=|_EoQEyLW*!%H=Ze@)x=Njv0E6os z4tBVI70)~Yp(5YacK@N*w9K+fzyM2ESw~P2ekL%{H!V3YJX-Qur5ofsU^7-Ps;mP$ zJVAM$af5v{linPLMZk8!aZ&G22Gi9qIEhL@CKemBMa0^q_h6c~-qg``6A!vY_~GEa z@hdsve`kLJ8TmcmT>}9Qp1uhPb;EhVyE?0=RkUpLp2d^)!LFx7>(em^`mCE|z1TfH z?c~06Km4X2^nLzp-9rhuw*-s@$}+LBQ%XN|W)b^DGZw=j2YsZqtXw^UZM(%Cu@nf1 zg_#l;&i)85rqujY3wpBth|x!PW&pA|t_IcFB1yEoL{uc}OED=Kh65o_cp`6ev?oWq zoY6*x=?oI*)2;^U@l2{R45kozSEUdgWM&*l8F5CKp?7j!*G$k=H%B2{JU5C4jR{4F z_Imj{xnm9aoMRrpN3>AR99@wIY|J)99BD_0nKH4q4H>?rdiLadt0s{3-!lvXz=2>g z0-5v^9RJ{Sgs95|BbpTZQhka>*&zBkJ|g7Jj_Dzv8@$N|r~9ax>6xKCz&}-q@C|kD z54q4FtHTLH{l~+~S8&heC+B(ESw9zHXwFrwg9DmrlNnQUYz>9~#R;03uiexlR^fZci#$}}x~d%U_bS^i}=R#HSa4ld#DIfEC~gzM)XZV zHmSpA_$T*xq>D%Ib0Pa@u1C#|PFtVDyqIqaxH7{I0!iA<+HUh7SIaB-1q(FE&2cpo0fg*m=J!Q!Hz)>@&r^6-9MR9=m)(k|KwRG4G zX8e08hjB1)=vd#$wjB(D?^;xAHLRwZ-Qc`hU;~l|p9)(!S^6zSj4|YX>TzjD(vmG+ z_Y+^>c>`04l!7PK;B* z-bwVKis!<09<<3T#M2Kr{PJK?9Adw+q<+ah-whWg8bEL;;+WU~;)}@Op29hD(UiII zyl7T(SLwH+{hHB?CN= z>fxUl0~}9JDaX@w3)YG2xZdSXDzwZytvqL45e9vjoh%P+^L>B4ABJ^_ zNmHsY)>Agr4%b{G^ig$4s;rn&G-R|dhjJlxp^u<~QkWaJjeTzKeC!tWy14zeSkzg) zsY&hVt$BuY#r+kN^k_+l3?`+Q-;p04h1#cBhmvI8Pl-;)N6%$QV$>29~PwYwS1Ft!9TfP84{{Q5pNTuCH*X}L@dzk!RsTeX9 zn@2j|W3$MeGnyFGPabV?tv8F0F3OAIj-37RIyb6@-w)aVhU`%9c1<~$8fYnF71DNR zrQA`Pzs=eH-PzWEcugk`v}gI)eT?E4pS5w846sD9{#K?P?xVj2N$6^mx44<%wUeLQ z-?{a|p79i_{!=HDO2(**yruN#h{gF*=C@4T3M|i(Mc7W3xe9da{Xx)W#0I%yI)clJ zk-}92)r!Y1DZ5wP%K`@%L3j6nn7x^u#{Cxz>~pFbaC4AHpo?+j36mocz^~pZxatzP z)^Dh+?vA10e;}p@?VXX*n9?$3uVqnm#3YGzuhnPM#ZF!h8~g_!V>_Lhb?Y|$-9H*L zT`*oT(yMu&I*#1q(e)2~Qpqain(V&}juAL(zT&1V{~GM;7i7!h`tk z>UeBzgE-u>DH+!98y*BdgRrH zYuDMjpnk0zt9k3#C2?`g#yo&w!KX20{)E()wS{m_6|N03h*hiH(LyD#i0$P`xhk9OW?DEhAN#K9f zD4wdV3ME&Yvy`_~tH9G3yb1vN@^}=NqRr!H3Sr7YCp)tjQ4%Xhdl{N<{*;&>W-J!n zS@2y9BB6Fd)S}jO5JS3sOn55RtgdtWs5zp8ipO(Dxu*59O&N!70ah;!zD6z^e}R8iA$Uq1rWE zZ{RxRBw7|bk3eCh{$+K^p^JZTRs3+*LHuc9gDFX~_?vrf?YVp2O^7_#I9!euL#O;kNq@VKWP>4q^TmVp|DBG4m!?T2;loNXaAu{pn|kzkLzXdr5v9VpWHq zg_3z?;S%z8xF6=m@OW|w?`qf0Imuu-pB@vV*2n||DAT*vYTMN3e8uN33%ncb(q%ui zq3)2cISu>D={Nsbq{OBxE8=0vW@WIPk}+(x6=w_Jrjus0sY9S5>6QFxu<8e72`gy)ykK zLH8T@pR5kszJ_qU*OAyg$xkoyfd9t=n27Z1XM4-!_frs~Ff^>S`c|Q6dpN-7w5pUx!3BzviwF)>*6PQ$e1M-%9R=MiRrAVfO(_I{rbhbuyiNx$yw5fVY`qFvZ3eV*w!go=Yk%!*9(dAtO+cG)brN!=3=753O!-{j*S-p~ zaQcWO5o9d`%F*&G^rgm)T6xlCW|%33SbCRy+HzeMzGV?$>%oC(d*TE3Q2Ys*u77)0 z%BP`sz3(Ey$D(_*A>mJNW;7h%sL_QT^%jYmXH>I&abQ#K2=gA2*chR*?fMbd(o-Yr zOuJ}WnfakB{^a@Mht^!+|BRM zJ!A~0;*izu^O;Da8fZALXDY)bW9$^-vXeg_Sts_=aSEskNO2wE_>@b9%?3}8g~i#B z4t}5+MZxySP|r4e!;_Yq$qQ9}dw&+1|2$9>P=OlARD_B*+9bcP(0QMhCMxp>zOJAh zv~C$vFyg78s6liNB>BTt-#GDx%+o~>2#Hs`MVCCv>|sG5p+x5X92f`oU6ae%uv6Md zUGjv>E~rRD`e^ISw&MN}J?G0pc!LMsco%M)U}HL-LFtyPWgHyk_?+2V^oZ0yX{TlU z8$qtQ)N>lH)>S1nZ1-#Onz1+_0P{pV_>KdtP_(I!8i6&1Dj@sYY0-H|)E#pO>Df z`Ut*(I@M8(U(_4Y@f%k9Ykt^H4or!OKPZe{?|1 za&a`@bWp8N`P;q;B>fS;>ZIxg6Sl%%$SlHb;m+8EwO+wosHjz=l!w8H8=_*>^#9TH z6@E>B|M!G~NGZGpkyaQWC7^Vp;3Vc-6da6@Mi|{}qeMiSQIi-*sUS7Fksciaqq}p2 zW7KcHKfmvPu*cnf-Fxmi=Xp-%fuv?aMQT>%igCsr8-*vz;eCbKFel2#Jz}csR<*FH zZdY}du9!oz?i~t}Z-UI%4M*0-$Y{x`gtfvd9sz@$G^x{aqF${NvWv^yz=|6C|Kcid z*dy7=c-XjT=AM^_Huf*-T^;j2cVUGQ#jRQ3%l6zvk##;8Db@)0I`$_cxF`_yvq|tI zAo}rNR`27!mTdq(9&~?>J0z2HLux`TG)U^9++eJ0b8xoSN5BWYGP-h+Ug?{MP<*~q zgp;<6UPtT6Lm4_rwyf5ijt|En+~=7htE34@XSpq0q<^abh|AfjZe@(LSbv)S0i8*0 z?6idhxWhWcofxP6|Cp@k#P_5^_?sD+`GEm2XlxZ-?Ae&ir30p-^a}8go6WJ{I5KzV z8C9BB%!@8_+t)v=MQF7)^nUetNh*hnNv_9kdcCZ?=(3u^kG?@mNe<~L@fzsHTtVz{pE$D zHFWlqEn)*ik{tp=#0Q4?d6ec0hIIUs_fb7EwcU}b4HNQhJW`m~008$oR zPdk^}3*d^l-Zx|{JH~syw#|ggt8RUXnmy^QgWRg93SsLwZ;OV%(Kb*@C+9X~JVQVQy1tCT?5}n-qNF&T!Rp&j+={q4t9;mRm6_5O>d05Ny zV8(R5C^Z_=E8qO5^=I{Awdn95KRaQsQVq7*>Krs!FCfzzkr&aLFUHdtza@!C`Po_K zwOK3JY7CY$VA+oHx#8lO=-rfT8FXn-`llO&C{tv1IdA)Jqp*A0|BOR#n#rgB@M5!| zSi0_jqZ#UY8t}mMdYW09!*0!4e+e!@=DJVjEfv;eL+x`y_k{aF&HUA|_+`~<1gW6h z{n9N>sbhUA+PQlK0I-*4Anx!%{oNFDa8#MDPos5kDI^<(bzsLG-(xA2sVc6W%vAl= zkP)s-xA__d;_$cEJ=v+uFlJ9wCNc}RH^B)&ryj3W+FFLwIGD6Jyz6poyPngPz?mNeo?Q1}Xl#4I_@Aotzqu$KGa6 zJ^K|NIgToVL>rztH9py;ATf1mzythB#R=28G<&iSi1=Fu>wbNXc;fxV8F84pf@yI$ z$T%$}SU`Nqpj}_HRkY~c>B1I>f@Xs7e~x6MM+Cj0WMz5|P=c9N-E!{uOKl?UT&S_arl9cCu!K|8Di!4?-JS2;u`>A? zwCQ{BRVg$Mz8t5;RG<}e|L-Z*@lC-_^Scxo2T%6$GAWTs?5}F)sij8kxC5$`d7(1? zGukC#_T9dl(tJ5@eo>|G*fox1+2{%W2YaplYyIgCivrrIf7_8)5Q5z23nCXS)ZC7{ zO;?wASo3>`-h~P}98D2TohH4g!<4?3;h*KZab>yJ;3ulkUpLU+A7q8WSz&;mt)wa_ ztVa^=K!IahkH;K(?wJwPsbEsYPL^H_Gbv`j69T7ij-k-olNv#Ig|yH zkj>{(RBHOjXz|IZ=T%*`m#jbf>AO36>1*Ew41c~5-eol_Y+`FxZ#w;D8=&R7*YMui zyE7~GGkRtvu{AqanRVb(etRv~xnsb&u~Swl&``Q(5KpL3gz{}8?axZ_U6m)s7Z8_D z+)xI|QjMvfR?XpnS>FDY-&CPUz`nLwH_D)@;C($CI#H@hH*a(LGlTf6(rIugh{0;; zPvIPWU~M!11@hU6LT+E9F*ccD`5&(RCk{dHC?9F*i(e!iN4wcOFsIM9sq)Ym*GqPu zZ?T)hi44^eHnYzFES}9#x&eEy-cqU6^}IxBmk-2vmMY)VOxk`b4(E6p4NL>l8bgYf z)f$m2cT})Wfgorl7r&R2B! zMTL}wwn{NfUXw(*cB5g7ZR_E7MYmiwxWLk+d98w=l}=u<*(ULr1QP>OhBcWbP@?TM z!tPbL{z_I0zU^vbWaW2Dt~lN9bVb9GZph&QZzkAs_PJYS@(zftK9|`Y{|fO6p1>}U zjb5&j?|7iY8GVqGP|0MNACq*FocD?LLdtg{bFQM?btNZcAU;YT+|#C;#PFCuhVXni zkndP=i+XjjSn*gOS+a?OpgMH+=PTl%@VgQF@1%a%H@fIPIhT$F zB;?V{=f7A$sc#-#=IX`Hg<53yt;Y-C#$(G8j)h*^RAe?z$%;zVyseU^S|3@ox^4UK zxap>dE+ny2iaV{veedd52zupC$;=9Qi=_b8CR6dLPbIi9)0oR?aUvVk+OyFAfbZaNL+d7R_zAfopkG+)K{Nc|=fGo~v!#85rekq4zm8bxMD1^S!_B{$E6*zn)8KvW zOttmOX5Hg?_@p2#&5FJL1lDgXIzv*`4b5$R)Vj*T?nstdLy!`SyTj5nO(AO_O_nWA z%{EUqUU5BbXY9$RKi>{@87vm}*hMg-J>q^>cTd}d+%j^dg8o(Z_d5E)w|D%&eRDM2&lw$x?v6J6R#dyWS?fDb{myIq zE%A5x$a%AlFTB8!t}=m@S})O-#;3yNPk&E=$+ojNnoYBF@9Up-U;}X-?)g^ti^ScS zPPN)bQDJJ%=`z^L3MpzQqc*$>PkbR?PU?A$T9OdT6>%dbJF#Uveus^CFlXbd>an8V zpU{|aJ1hG^$w7aO7r!)F zX3FpVrT4>j@AvMy&wR*J_A=7sc~ixCqXMn>cPF{s&MmMaqm;94mN=SP*$khAe^b+! ziUrI=d?_DS1mkv8ks~A?%n#Uby@T6Z8(MJdpO#7X-s_QC9YwAWEGcZ4Xa^#rWn-g?Dr0SX zHMC-c&8I1NG$j>bao@S!`fs0iv8?by3t8?;leu#-%KI?n$@LX}yLWIzU-oRV%@mD5 zqwoCejqLdJGipH6Wk_}ioj{LCEDd-hA^H84*H_KX>J*3xUph?m!MGUN7`|`Y&%$4xM!w<5AO)B_{2AJn!*E_$Twjiq#S_V$ zNBAbQv41ed zzaiRB3FBMA%rwiju014ULp$otJT6&3)S+`bIU5}pQ?v>zKQJX=i0qr%Z-X{K8gou+P3KCh6SQW$Lb7M|3qZ^CkBYa+*w# z(a~D;!5m;p^@hq;ILR!{RgD)l4ee=4DsDUn&p+n0Y%z!!um~_f6)uU$k1S@`v{vei z^~{4c0X_30=MEYTA_B9Md5V3~k~>sn_kboWuztd+d|s+V(T)9wA~?hk#ad`#m&^5_ zMx~sn-xRG>=KV`8(O%h(WQD<$#m-w~X`EA_ow$qiux_si5%Wnp+x`jAo%=PG?D4LL+fZ{hQir=pcR|)zbVjc5H>Q^=uz<{w zVnU>ZaA-is-MWZxVl1$a3k{xvfiOx2)Vf-DLiOTug~04@*ksg zl1;xk4kaLW+MitqNe5f>64|Fj{4j07oB*jz=}h962l_HNVT2Z%!UGaVuK479h`02R&Zls{4Q|Drp9nKKQh$|7-z?u{ zxes8&Mx@P7kJFC$+;3B_b;cu))74|fe8>!b_f}c~#Xd~B+3VUb5WAzr%s)C2w)4f% z|KP*al%8@v@yB@vtBEcV(=Ww7P<9#SLBL;P{ zm(Nem>l6@)`S#4Z!aeNPOEvl_lwU5~NR% zQWKzQIRK@XXjdX8pW_S++1BAeW4NMyR|-?Irh@hRp0+^K0tZJ?C1{X%SaIPk_>`6r zEobU5)Dqhf&B^RxwXMK`K2lRr)$~7NByg#0PiRg8r@qs2vN6evT@soDPIQmO}SwxDHU?xWp*C*nKvTfM_soY&lI!M7{CUOJ2df8qJo$;_hr`TT|3jbnWFiU3<#SsGbSj}T+ipa8hjj;%=tf0bJ9%ZT$(^C&kKX!or>l7n% zh=NnTMX=^N#n;?vR%b-l#6?M@UQ^EM(+rAwEU7X1R`ii0*{Up;2N5ho=AL54oX9~y za#SKI%7)W;02@uLK9!-&smD@YrwL~+#~-k~sk8xgge~fn+D+QTY|@O?7KW1kR42(j zt_y0_DsNR#K*e?Q9C<{ldwBpc7Y{*k_!qp#nXOlMWjxBs1u z?rV+-S!Qa<*kt73zX5W7d;GXYB9Pti%2CVOD22AEzS1ObXu1!d!%f@mM zfehICYNRz&(WNXKJZh=*amiymSXrAB&>p1hdHa5W?z;j;lMEkq;s?16`??By@aZ$_ zQjPa9$RliIz+fDIh3sSB`5ad-&FQE=DocGt-H6A>dENc3eAjZ=N9;1HJTBbqNyTnLBrUm>&dX7cAk@`Unbb2jSyjbc6(YX3zfOqQ+u7N?yG zJ(9J80J>$1_d$sgc!J)yl}_Hd1b zO!DaXze!cnhCh#r4C9?TyN(SJUnTZ{R-9ob?x)&e+F$=sjZ1 zO8%CmlhX|I!(Qsv*qv?W_Et-TrRMiIj)QISDAR6H_N^CL_< zQJhm|z8gt#_WA|Ml5pbBnDTLa$rJ5!drPH*LjNf-cHW}M7@tYEj+9P{=`x>Rqp8|t*}LJ3r2h(TT|Dp3hlbkd-<+_n+p-1RZ4 zlejRahDd#s>!X#91$eE{RA+LMTez_dkBZN)kse4tY%5AeSkcepsD5~CW(B@k){%5I zGuHFMKSO>dUEZr+0o?CcqUE2V;4XnyF=qMWZoMETKUJ0FOyekT+?0wqUoWMF#zbD) zi%VAaR`Po8`vmt;3)oZE()g~QJXyD=oG#QuM=DwX;Y2by%h%GpOKn!uZ252?$~SYz z1|CQpZY*-NxUj2oDA)EPVOk-*cY=ALX)M*U z@-lil8hk?(ba^^pD%*L$YlGUAsGH#hVgKgw7WE^aik9&%rzj8;7TSvC73_Ux<951p>{#ie|e3w!S= z&MNlF>5`w!A2Y4Z*lc0(ke(j5#U)2}t)7AE5-B0wB}?HQe&znA%RHQc({0)AFvTo{ z2&wBb1i8G8TpyNpx&>K=>w@G-jfLxV&7|mSvR6BYUBgwU5|T60Q$W7A5`^%QB26;KA|ujFP_mn1@aM!$m$zT8sUVmvD3o_;o`eRu@_ ztJ^$^P`F6ZElPdsSVyqr_U&KA^$r&UH!z}gHG*E!cWO8pm5v5W9(?;*d3i;;WYRhV zTgq9wgF?k!vo6^(htqC|2qRsX#8(U`WHMYUeRO%$+_ayKzA3|s|^f&T%Ojg6WZW7@zD+R5qNElB=glgs<7A zQ|(3`@oWv!uLACmOyQ_`SkmCgVWV5c(h_ry7x(5CK6Soab*>FKO%7n2p{U-e{0{hp zB6Q?d>UHUk3?k}t)&vB?8BGjDrMFG z4kVzHK55jn4I!2LI%E->e-?D~bYMC*X(Y^>A(drzF0eh@hMFN4uy!dbju_lV8#YI~{m*hf$2rIF&7r{GNaUtkX-HqX%g_R!6@m14s| zgN}y07k4>%-4b$#s&=yRf|v;hO0b>d&2`MY>Fw;^yDg3E*SJ#t#yYaEM;Ka#XSiF( zrCu(VVksfV|GSx7ad*%nlndcsi7oMl4H^n1lP@`|fm)R@8Itpuu5$STur${ax8Yji zCtm2GY6kNIL&~@3Do0t2o@i3(P@TeFJ;r~tTVU%$7(=pJvB;mjxIb{)<%J5IY|Y%& zd*7F_QDlal&htdvI#F=j%igY!Y3PtftTZe zq}Jlb_RtO~dcF9t@iFH#rJWmR%YcK}KNC4cmqB8e9oMb0Z^qD*#kNn;3(;blhQo@a zA@f6j9Oop0on5SsEfoj5ps%?67Mcu-%ITA@rZkQ6@qg*$g&s;sr?%E*K*VN%5X#xO z*vKLlbr&MH&ZLzmS|A|aGY=1`NCfYcNxU6slrMTpH|Y#j6OdAKlWt`j&>}g!c9V>q zHX1LFi^siFPt$}L4`+OqqE^P5EB$cDZszng^cgt)7D`Y_+pVlkOd2O!GUP+@+Sl#D z*XlS`1mG8*)U_Amkdcog#vRc+qrxRO+knaf2a_))osOQX?=R~+F+=5qR=(z^!Mogu zNNGkLg>Vq2;+x}Ge)}=~j85u=lh}3#SvukcN?CiZys37=GORI5>>|0bNY@9#tIDl} z&0)EhEV&=SKz-PbkINk5mk6b*cKQ$7+YyIR1MmZqG^ZwKsc6K-lUhw~K5u2gQ_^Zf z7RLrbGD-4LzZFhBNxc0djGpQ~B}YKgf`bZgwe|9lr8=2Ks{hJFh$iZ8;O2(*)kc21 z61y5YO+MkkUJ`%}@7W7!d#-5Zab#Jj<1## z>DcllS8?66s@lTO9Qy{Q8$wKrsJ4HyU)x3@7 zjUG_jsTbPcG9GU|V}o4sL1vUwPUamzxJY#EeMa4HijOuX%Me}p;>SnXO4vx6m5uC) z-_kJa(j6Oe`GdIF_Qu*tru)cCHcL_~Eq~#+3c_*M4A_#(YFI3`lJ3Xk*C;~uE7jT`kVO>(C(8QMQT=0JfX`?c zka61hn#j?_JN0%}uL&1O=^FOEErk8`>dmX{-JauQXydO1oyS^r%nt0uoz7qJk}2S( zHi@{EHe*wCok1f=^Ilm`oK}n%DrYJ0m-IiwMSPy2iEl53I>phFn(M_~<}sJFIRPWZ zS+;uY*lf&Wr;nHbc00=EzeJsyAnnL^NfbIqAM`@*5l5kFd_9mj;xqAfAmK9o@!caU zpE2BBeXe`Rh;joi69Vc#)yJ5F4Rb)Q+qMA+={c%L4_I|x_%U^k%gQB%~swXPV={tw0=- zx#fEioGkI^I7jH18s=#{zo=7Li$LraCU?W}z2o|p)VB-U{GTT4x~bkwhMMbzavU*u zSU%@k#q=+#6V|PLSvVSiKz24`qHECFcE+<*fmC0aFJ*JoE^$EvY)Z7XsbtE8R+J(; z8P`cqeqL#xP4F|cFa<=HObb0F2(h+fKCp2*h2komHj*%xgMtN8v>>*f?+(7AnG)-% zW#!K<9JD?)v>l_4mPsE81iYM_tpV$zl+w=L3trQ_DYtO~-3k-IJwFI`eaS|9lURY_pe z$)v-~Ur}2n8C#4ll?#j(p;aOh4?dGwTikaN7X@Ye=g1`F68zSrA3Y{!maI4!+OK*Q2piJ{k)A_#9}4VEC1jX7tSE&U=6G+G34Fd ztFZZXoK?J>V=fo=^^@|yM`B(N6T-^>cYHbek)lx-A<`L7QN=>zw?$%eRy6ea^D8D7_QyS1w3*b(`2M2vLrgVc@o9_uD+`&gHh!M?f6y1&%w1&bGgW zG591QS1ERV$cJOHM#46x*f1CO*asIL)Fprt502>Sd7+Vi|1Vk&>V85Vl#ED~Bxam( zw9?E0>lrcz;tHt`mZ<_mM-oauuTe1Sl3I*w)a-mG)32Gp5?iPaB*N{D>oO8GY!|#k zv^XO}|6H{UtsOSnVv8w=Pg}-BjkkW10~Ds(jA%Le3__d6U!7pDs0gi17lrxg)J`! zReAR43RiVjx39>)i+p5YlwPQ{Xbs^Is`?G^_6xo{{N=BB`!@4CYCWHyiZz7H?nOPP zBDVMDZ)bS#kEU;F$0H1`u`}29TkoSJP1h@*4Yw~~UfR6is*dm>jcCEorL)4!FsawX z4lw3Y8wbATuGlWsrLxN>D;IFF+4 z1-kK8&=)6RG8!m)_>9(+E0+0Z1?t8gPX(PY2~h1PM!(@!g}=7yMibo3I}{6M-8QXz zmmG~*&#K)bLBr*Q`RpFquT6&`Zyi6K2Hsi<7B_k?4BbjT0E$Vj-Q1~>%&Vq6>N7v*Pt5H(g70-w5eI+~nutI^AydZ0gjne9h?xj<2) z^-gxr{nZQTatco*vwV0cT9_|k;X$%ARKYTST%A>C(N3z2c)b&}CEU{$pXCY&iH^X> zc}IIrc_(mRm-lb1JO8Y7yqz&lAwKom$&lq9?Qx>NS0W<;B12?o_S*E-DE0iRaFk{I zLzme0ow;)}7*wc}0DJn)DOftnG{kSIuWIY5UwcMDZ87|~akuUJkSOIcN^s`&D}eNt zA$!FdcW3NVlU|{Zoa=cBK!`FP^@_C?_6{4IuPEy|45Ez-QxS44B)*!w&$1Mk@%T9L zwz?ML)eG&J}z!OqMGr|(wZE-Vfgu+!)S zlG|PD$?;$A3ODLZpYrkwb5a@InQqM9(e=N2dd`lpuw|c@f+8MCrd=qe(R+`-i%7W zd$qsK9oFWM=Qut1R-|vUpq1@2Ns)D&iF3{JTvStmK4`W15Ge-yls=>2{l=GNziJLv z;9m=b55X%20{;4b!RT!RtK&gVEqi(m8s?S>yJF(Cg7Z{y06rNzk z-UDg?yBrxQtp9w2y=&NGYmBZeoXZgqGfSy&ThMp1FBgBz>$V7;YIV@Z21-enfm}j^ z5q&g_W`|2GWZ}AHr?x6*U^qNUV%1n`;=XNtbe;6ata`+M*v!$N>M6rN3Ux0rU?*2= zo1pbCOv#m|vC`(|wmOxxA?Q#xF0fpdd>2mvOz&RTO9K_(c^lXlD6I#CaM1nKX82Fy z%1C2c?w(-s6>8g*fIFO-b@N5c%@))-{4vo7lrPQD%W$nb3C``#)z_RWZkA>rw={`b zi@VmRU55%YUe5TQaas?}Nqjl)PcKID8Y<#M2=6lo7$G&b&Pwin(F1f%4Uz*IIOWm4 zlTyT(H%Bpu%LLQ;${*fXJ|lpH7C}17R1@{XnlxB|E=+-QP+cmbSoQGB^6oJHwVfvyw1SUcOCJIp73RRYTB3>&_GuH;pNsVb*G?xe?z6s}gk8 zYHq$PqHC_({812bj*?$!FyV4*J9%>?H*Zk>4+3&sM9 zu5$V!w%F~Qww@I4xcU(!DjEfQoZoGh`(YmfL3sve96~{Uxn%n=|EI0&&#XZpczQUPoy=S7E=m1`p8$Zr2{|$8ua`d)W zPMb8+xAh5Tv#3RNFE&zC(zEI^A@NxU{k1Hl6Ff@ zUc%yfkt^Upi|Vst^cDAelj`ASb{bo~^kUk}=V5)*>*Gw?%ejroo_icBMn`7FhGhEz zi*4UYowXSo1kaYefUC0^_kpHYTcr+v`giAlRwWjY36pxGF238QI%@f1!3MJ@7`oS$ z?fL*;p*@M_m`01xNLyZRi?l0+{5ZNJ`@uyWVG>rvQJnt7;@NwS`K7ObhhAc}YyXm`+) z9h3~#R%6WLYd|*!!dpU7ez7=_Y69~E7=RgY*G1rtV{F{B&JT>+c9f{m&X3T%H1>aZ z4qTJ^9Rkr+I2TjQn+KMYdN@53dFS)MOlZG>~`d7w1@PVb71NDIX zbtZ8H8ot?0^fi~hEdWYnyqQ>VBOjF9T?%27_1x6)?yDI0^q%##^{yxEoQaZcJz?ZS zZ2drR>vG*fX$oG{1ztv7na@~EcHC?RteSI4 z*(ur3^MwdtFinso(JJ0{cCZ`qjT+8W*(K7y56%=q4u&!$tmat3x_FmxH{I||S`6|; z0a3@p;Rf3D<52NXd85eyvDprqQ9O_)^AMVHPU%N6mY;wrmXLcSs+k zpzLBOs&DIIFEELcl=dmvhxulhYx{n?Roh7qhO1FlWwcEt$3D=DKc*bF*%YQme~5H@ zfUUdASDr2>pWU=&gRpIWZG22P$_0i?ZxyggXSR0g5uq14W#WwfC=pBaQ?L<{GHiBO zq-rNj)CUk#Wxew+m7$scaH5QuHPrTWFzfgfm^wx;2bKH?RHSW{A_H`AFMR~P!vMXI zpsTQa6W17S!<)M#ed|lbljdV+PSLkT5buJSiN!zTj7SzQku44M z@2=sdqcCnsnjosKx3p)Gk+y^Q`w`1fn6PheFu8jqy1R0>0k>k6(ua#MiQnLnQ zWo6Ar$wx_HMX(*6ke|>V4h<-eu@$$>dri8O$S^2WbmsElol{r=Qm4&(+n`$zb z2|)vzUxY0jS9QB2GbVb=2z4|e8D0t8^BsRZ6OYzPg(w5{zj$9|Y^9yBys<2iCorLH z4Abr1xC0@P%148YETSZM>=dVi|Pw+GM( zJ1lA;e7HN{6-9mivga0#4nl1jG2(-7UelfEisan6nG3dgS6JNB&JXGTr%Ok^OM;g# zn%>T8=g|;PYfgf4nhu^$`8YOL36dG!p2L`Vd7$NW{{ut6(o0w?MsK22%+c+6S8Ub; ztqv;KIv2alt^HWG=A&@WF5FmKh4@VnC9HjOvb0 z)3gM3-j3(w_PNU9M|}4Cuq32Iqfcd&I*;7m8*6FoSsZYe@V!pv>*R3YK5?!NuuK@` z3UMuesCYhg>wmNto!KeYP%nw$s_*ON8GqARU9lds_WTa}r^AV7w(+7qRsdSp zi;_pBvKXE(<%uM2F$a72LLi+XO|!BYWv9Xm`RpRL1cPfWI}AS zt9sr+1Kky0&ihnlU^v%s0nex~reUORsXeJE>lG<+U|)F@ z2)kgEhxYuGP`;vF_6mgNm`s^WJ^Pt5TYi`o##QTsBD2&m8vl<aR1wCM-+3iJVa>!jwY+qcA&<9FvGgJ)RvWq>iHK@%I_hA zME-=Z-SV7(_5a&J+Xp&WyAi#gBZb2@?J|)YJ?H-S|-YnLIey3c%tNY?R z((NajMCqhm=H>_3|0++Fh4rZ0Ck3HMG6FM*eQ;{|ZjhF-QQi#U)XZsNWhLNtN}AX?S2?h|Ec ze!#MvM%YK^fw8D4dP)ygcdXS>W@&Y5MK+y>m8>v@-y5>2j+>)Z$K5^IZRq1pt;&#N zGNbWHGUm(M)HJ^5se-BHS|rgAqwX0xnVJ(*h?8eCo@BGtFgZ==%q>^Y%gZfz&`>d# zq~AXgXm87;ble_9yp6?(I9emc7>%^YO=-hZ5!npZlJGPc4^k<;CJ(b2nbpA>Ud8g( z%=S33Qa1^^-Bu)CH9A2?r$UoPUmH#M%VQk$@c7h0xXzq5*13gj#AdBbn^zTo(3NmF zE*?(2_XOaA!<;d-+LVoYB56893GXvG_iX7 ztp?hhb3KDL$E%c5uY=3l<5*3p+&+y&1aF3<(cGhva|3KMWg~*RvYpvCsFhrOyFl86 z*ZqF>!k(qyA;W9!6}E+#2_UXbVB>yrS-3HGCY$l1kU~FoSr<8Q^Lf6Aigg0g7R;Wo z@}~7b+&Yo**q+V!7&Iz4H&)RQ=G-ZsFAqW;IVx<6^WC93X8F0J%3qnK-svbaEu ztiD%J2aCtHC3RgJ1w*UuC$EyOYZGOT0j|xM$o*70fdT$h?Q#%g#-2+o>Ba*G3VI0$khxOC4yK%f*AKNe~>? z0`hfTy7hya`yN|br1NnCvg1I81V;|CxU=M&mG60M`<%z|Zn1L7!f~aVB3&b_r~3na zGIP^2h_juP+StKt?n7bJIRlyX5is0%^{#5C_d!KL#^ORYO@$;CIZDO=etU8NwM*EE?>7=)I?LK$VM!*>UPyWTB2q{3{2BUAWF1N zx^zBFn1`qhD_bkaV5ulnpqd8vPILyJ<8wg4-_f%22^F}>KwE`PaqERDV)wYI@JQ0x za!0pdUD9B!dvCadoP9jZSj#$;Fyy^8;#E1&qBT&cXl)fboXGlP{1@qpv1ewMJ&0YO zi$pDJ*y@m&iE*HoU(DUHF66#4lVcywD;i-i-b%Hr?R5A!Yg6y6Se-et8c0K9PHA&us*O9|D0 zc^G7FJ2Et9y$b`F!}Qw^cGUHra^D0o0w&S!$8 z=Ez#Z$$^v1FIev*tKBw@sO}4Vn9BnyS}S!V8K;z%*MoaHB1Ly-@=py}&EP)6q-Gl& zRxKL}^cXUSB z2oB?akIkJ)zu!s{gOhD!+u95|K6*Fg$e-_x=-!LqA!dN3P$e&wjS+iX{vv7na%XFi zsPWI$g#(M!bI;h7TqN=!W> z2RuCL9u3tcQ}O+*B+YzzE_9i14F%|==YU$H^i%MIjxlZDvpskerHDHFfEB@r0#fTO zALwlJzPNMR?qlLtVjEielc>}=_|@cdh>88LoGOWCWL`bVqimhI^F`4bpth|0GtI-1 znHF_PpL11rU|p{&z}w?9e-R)}7Wu{wNy6)6A*J#;QU6^OL6)UQdRzS#7R2w<6bQg~ z`8dHPEAj?s7x~*fj2Yc(UnINtT91^xKIuS?V@ClN2pV868HcBSZ%4#Zp~UPtEo?xo`uM|DT;@|dcPe-&&;mkv^df? zu|(<=kBz#1Fhk0feZ%6ros}A1Cbuzt- z+wC3LZd4_-)Cy5gQvi~GVl$t}+24rjCU*w=X2FgFF#sy*2xrn9QW-HzBdj(CzHRAA zJ`l<>)kbG!VP5)duIFf?1>nKrzB}ap{Whn}E!F;MIqd~YzP1?UTbziT54vO#(tq|) zJ|*43QDQV#gE;efv?TK0WjL*|o)Iz6bbkTNxUZ9NW;YQVRwU3t%?F`;C;?vWHFocw^VYbsZ1fyf-@qWHybZbMO8sR%wR5PPyw}SB zREN)TC=SJk;)NVEQXRW+f>Mo=5?ns3p;3}9Ngs4c{VR<2mj5XM+dd^qhyb>ytaG%+ zy|ZN%oXvC=ZU7%r@ZgA5$A-4|fmNn^WiJ~=EKTqko|I-`CyKfIKqE5kP01rnrd!@Z zTkAD78{SpJ4q5lLO9k2|sbr4T;ARzCd0Qbut5~hbKIBa%UHaYqPOu`XJL(xaQkk8K zFrTr2K{YeF@@ldF%MFn1KQHb1O)brq(iFOiK`T82qn$*fF8DDltS0TDdm*;too9K zEyD$L*?6V9@WR9A`eUw|s2r|~FKx1vw&yNyq7LH2546y>BYo9h_^mIFq#|Z{Fw%cP zG>hF@ncX15Fsa_DiY$%ngF%=YLtl#+r=TusnZGGFM%W?d*=so9)88UAvE{awVC=PK zzE%tD(_a=BxB*0^>F2P1+>;3L2(j>INoPAE)$3(GbtxE$d4U%*cQboFcskTP4VZ#f z{l3nL-bW42Gc%`NTZbf?V-{`5uk>OGHf0a=iU2%4cW5@lIXx!oRb^Uw=w{p&2UmeT zzcn(v@*7nrK}N&@<>5u4DvtVr0kWuKy6e2i@?F}Fd>!sKfMxVS?*!X$ z$A6zjb7`iOd)}rVj(nLcZ$yD7v|3w{I@V)BBuj36#=d|uy?5RL{#@S>>>1isBN&YQ zMk4#QcSVBtGjGe_2fv+>C~*^39smQ2K7jtH8a6=DAKahQd_R{TA;f1sZa+fQCnNWej?D0%x6N|?VV!Lq6wv|ue;W4ScWm{8>CYbxxn2+clYX$! z5!4;0d&5iia+HQkvdzZi6aCag7Ti%~eWH_5(V~;Zt28v)Q~l%e0keU{X&9F>kW0gE zaq!^TD?9hXvxt;HlzhOMl-j!h`d2JQ8a>#^fVXsn5~# zd1?PNhrjvOdp|a4hp%H0RoxiKAYrfzj?L8U}wWX^PpswKA3D$EX_zJ;A`jwrHr` zgu6sXe%V;(Gj0=rK7q7aJMT<>%Ha3EJ&NEM+xe2WDMn%xkG@+%#;=^GgR0v}14 z#$=V3R}8sseHr|JG`;sDn~nSa9kf!lTJBOsXem{(-L`~kt7;W(QM0xZdyj-t>b9zO zjZj6E+I#O1Blg~;HZfv^$dkUmpXVR&gXGF}p2v9{uVd9VaOOn!ZT(RFckPc_2#RmH zw5VZQtRQel-QElm_*L%9W(#=BI5>x^Ln2!p@lb;AXR@cB&ArOsdA;%uXi zRJ`&Br2^beP845cnb+yA&uYAO6rGYRZI!rPX72yy<8>C&*l9Pra}gP4_Wn7S;@Ux9 ze%OKiY*sj(bum+Ea2qJquax3F&l7NGtil(9Inv*RDSA4NdVxpn8qUCraYE!IdE564Zidn z{@2`ieRe9?cHQfhf$7lYYfqrW#}z^z9Yml|Vtn{61?w|DabmrB`BE7`yv#)aS#4qA zs%$k4TOv8=lhDTR zv6B_ac*Yl4W@p5>6|Tqvx5#x$hwv;dIr}akjLdsSKHF_3z@g?@;=jt^Z<7qm+7xJBZb^urA9lo>F3j zT3H_V)5uB-O_}h%NAg}HAHPHC6y78I-Rx&nv|FBBS{VmcC+@B)r{$GsIM}i%ddTi= z{95b=Gxms06uTuwQ@!{alUeKGN$w&^h_m&*XO`AeAgIZ1n3Oc+s?xINdrt~*Y{*h(&qy0q?8vNw7YNGN1z)&APZl6Gyw8{UVo|nNvxa>UsdH=O8uIA z+^Lv16eW9ZuL6-HMdlN2?j+=Y#Wm6lAcd)e$1D0Rgz-H=v-6k4 zF;Q5X5p6Zq71O?;%C`F1zxGs*GIv8ctbMnHC}9;*DoDXyhd3G3<~_m7PJvmi36*9d z?F%GNn?zNupOQ!UK*F2nMr!*x56w*M1)Mp_5fqC#UEtaVYHsGkK55j9EyU4obFrJz z!?Li6B@f}Vn?8+Z#8&+kn@g4a-T6@8Te)#=HIes1hJ(q%oOmQJ#wDVyEs2>T;A?!S z_(*606}_zWvW_<2O&1HB``5fFe=LA6d0FgPP|g9X>~Bh#lCSJ5a>>C+SF2L>*>Zzb zZIY6%(~UY!lh{Bs~vYCm&=Tk&oU}5 zf2Y;FPW4Om@UN@VfjpN~CpeQv5!;!M^{KB2C8YX`*%spLUUa$PlsDOzVLr|M1*gep zb8$;dk55b7gS)qy5)JeguL%r7e^+Jm#kOLpG`D6mM~pTOk~6PHGA|)N={*K|l%-=J ziuULY5!fT1*Gb~FatU}~hFA6;2Njd`SGg0RS)e>_>>0?IGg}h9@x#P3SRg#gZ6YFgB0g z$G%Y8>-LNrEWc4~hA*;nc*`P<_gam}GDa$Q;Nr57-w}pS%(ka(lIqjbZzdLyM+?aU zvQtD)+L{@~2x|??glw%8(|wUZkV{T(yL3=-UW|Sf@0Qer|6j23`pj&fAw#RAHA;Kt z!!kkpC&F%t&V#yyInre~rBvIEDu~hk?9ToX6~-U8lp2@cPwvvi+uu*-*F4l+pubW5 zg7EXx;r@S$`V;i6nS#Ydo=e+^^mD|P9&!s#oJ2G=G&SZbS!Cxsb#{zXnwnYZKAi}K z@xQRRU(xm*mgVlb{Hp8}NCxbj#)ot5UA2@1Rt2c|@#Y82w1ZFu(d4glYG-hdK^U_3 zuzf8%nH(-J>B7^RD8OnCVQhvKjo6LC_|kE*kzk_|o5W|90@r0}o*t-{Q;I2A#jO5l zREU*Nd*OL(E1OL~+exo}a77<$ci&*FJ_K+Rr_1P&2VLU|2+G+jdPdo8@LoLj~N595(LkgJ1OZ(twx-1}y< zT)F@))YYXRL0q5yEcXdq5Fn?|Xy?qQ5fDtZELLD~QHBB)mPnM7tvWT$M;VV*I3{%L z8mk$_dCzl3Hd@MY$27c#AT?706u319QXX z(p)_)(?~s#s(aCmILjzNwrdgd6K^^dX>#$!JXVEAttP!bVl;WP0eqz}_l^DC+7zyw zV(E^O^FcugV_)DcHp{GHvP-J>|1yH5N@=61Cb*ro_Urw^>WLJ8xI1dDa?66maVgxb zy>&8pp`kt+fD!M`Hv2uu!MGOrSP>D=>*%`k<%_d~2WXFHlj{*f#&uJ()-@fQ`{CV` z0~UkV4E95uW@m^?s!%dCuF-J2&702WkEL_)hM3sO1rU0sY;s)Z@iOdr8k4k}y!{69^E z+k<;$@$L_?S878U#*fs2X1u-;84=fzbRL1~9nzAP%qx=-x9+97jTpCL1*v#l&2&u* zX*2F>pmpB1IFhBgS4$Ck{0%D79KWEU?ILWDv!xJEsK#t?jf@4Bes!F#0lUgknTD<`*!_E@G@>@MbiVH0f>6tF5ffw1S)+Q`msit23Xt+xSCB0ogS`(8$h#>$fo5Wk(xDuo~IuPS9jU*D0?!%x#CMi?&eSI>PX_vOS{0(0o*;XW(q6@)KTv zM#DZs%@5)OvM3I6;8Y(pOR3l@C&X}8NbV%UZ{OK#0o&zw1E+<_6f{0}Ss= z4#%Xap+T!SW(O(FRSmEGUR9r-68s3&08lHByVH zjw=PVT5@7XS*L%@Fyi}i5#{Uvf>@a-!@CQDQ+D%B1qnyM%!J`0IoV;5MeH+|F{SPx zT5|_fr%b<8C7T++OD9a@_E&dHg`X>-g>{qfeydno>kM7C`Pdt*zHNMX(tIB_YGeE> zelJc!2P#onBvNZ@H>M`bFNSvVsGOWJKeTk?ag9Je%MiE48#ClK{p^U#+4peftUKd9 z{_{kHVf*5;v$f%>$AXbG7U{K=qXfR}zPa|8(zA2VbCRoeNVwZZF(|Ot>&NZ|dBs+S zz^brs$*9CWwTdYYTM^Ip%W>W&Z!(KAgu*G|4nBNt9HZv6b$f5T}~B; zz#hq~vCj6;ROQq6jm2N7wP0T=rQM}n3!viOPCylo54ar|jSVUqOLd~+Axqn{f9K!e z6B5?!3M*kfLNeW)<3}q^p(&LtzQmb){vliN%H~$wuui7A(<}{PUI1n{}64j2O)2Tmh7>RG3Gp>fR`Al*ZQC7;N1_;l~yRf9Vq(| z@%nwKm}0*wi~R*fyf}pM6gJzP`zq7zj}aAC{#sg%7ivM)bTDk;QH!XL9_rJOge}IS zLAd4n#b&&=r$~z&VVIi*f1>f9+!C^SeTxFtl?bqFD0#Ga?@_!+&8?nJYj(G;wtb;n z(lij*OEj*RgygiJf^{Op(CMdf#{vm&Q?XoS zY)Hv0yt?ViR8Dl(^xIxlXx~y&Fvtk?H2^)PE=NqLUJ3M*`i850QtiCB;HSQ^F}#X5 zsMvfOPA|ENB4o;l5qx)Gvo&LIkLu&5`34b@$fLAejH#)`di+$*Vm3ElfDUprU$x{% zro8kA)Igdlz6*M?IosLc?B;T(HB?x?c&ZahJRYseQ~-874~nL&Yr3g4A2E4Cp-SC= zecV1gf@!(@+bY3-EY^zuna8Q^QOGfWwqpk<~Sn*kkoNhSVhLVQ_|bO!6| zi?)tC8~!(`Gx|Q+)PMw5o-^CWykCLX74`jd(bg!BCdw+uQ*qrtJw~_a_ z)O{z9Y2$Z5^YcRD{AyU{A^;$LzmG4m0>OO{|6EEKQmAqfD^E=@DL;Q6ck#ZrPrk!Y z7u32{`FG1HHR;iBGxNH@wKP&k$Ud$h8@3~nq zH>wF(U#zi=*T4g-ZL{lwOHH46v|(c_5@9(}o)bydu*b=w*1E#5nV-MpZFO&Eh{q))4&utF`G zDky!v;=oJDUhCkxaGep?>i$5U~8J@Pr~`L>wMVn$Wh zH1qAG!z+Yuo4tBnW|8 zM>l#r4_KliQ>yIt)dY5#)!8vW+|c>P>NQ0gNwO1TZM6}}g3pc%7B2@D!6oUSpTu?v z$H6n`VO*>S*X0s$JMj_dC0^A>MrA#*5kbgDJcbJ6o$*N&7&@0vK}J*}O#!9;D+l>o z)Bw3toNA!kab7Sn?k?K2;zY%aqyk}1qjz$RJ2Jes^&Wcto$ba_wYR$VELHRWvuqBP zxePf714AUUs8rq=c>!n=jjrV}?tG0F@r@7oG3REX_ckzaRULFD2@wxeYa)Az(|>%? zB;hc~^)zA*@L?QwJ>Xm?p#n<>IzL)BZ1mM6AI-eHfDFd^EzG43ryKU9RcEek$Z;E^ zhrV(1Mf;oUAWEwzwqN-rmffCrT^pn(yBwrwhNPU?gtCOKs%fn4b7Vv^j8)4_<|Ol? zU=Kp2g$Eu;Xn6~5REEPI2zMf?hiiRi-1CUHY;QWpHz1*nO4H>x>EQk!@KY*|g*~D#*K8r2dhp^cl8bP;W zM;$gd1zoXUlWH%0?^ln2@V(+*sdEM?vQ7cTr{;Cn{F{yWo)s_Ll!4CB?SL17wQd;f zeA4fLL+|5GNF*_s3Mg#2ld6%rX1Jl~Z&7ADyzchcW5v9@`J@$J^+BV!n>tu+QbILW z`A+(wh;>`VT>eW=R<%OdV%gKE?r->Sf6SVP>Z`4H)iIC<^lX0(&yb!nZ=M%{J>b%u zNvlP%AAWflR6e;UrrGb$pd#Ff0w+Wxb@Ox)fjHfDMl>9c>tQt$h`EbS#mReIKDf9i zBpYHVKJ##vh0HK`XHq-N10p2B@+B;?7Aq+1L`Xax<5c7$IISbMHlj1>UYtHtA!PtQfud5-fMY(t^f0jqpOIK=_pEH1o5sp;cNd26;`hqfFQReEH3v^7bz6-%Gk0zV?DOzF&<* zhC3il$4`U*cPH**|H3X< ze5xUdyqH@T7Nk0_us$Sdu#wZ;N_1eBf2e6gdfQOk4`gMsLeka*d9+hJ?5rHW;IKq70seZHJ7h#*IIy@W4#JQI~U? z2sL@7)dR=thAtaR6Z9N2gC_D0inaW)LR;7NCuW^?wUVm^pUhX6x9-onzo{Y31kT@) z_wrh&4P<6Cge{CHn(-YBiH>q%azf5gr#l0D&p`dTZe7^q-kJbPv(}CN%e1q&-K3*%)3Pdpf3{gwZxu_7w)j<#sW&r0^EE#0+?VOKVuJXF4`5<83iv|CkUhNP@j+^rc zRbjTfP7oscQ!#H=aLy`~tzDy$^tT87rEkc~ze;aYTF3A*-jfj*jB{(v)#L{e<&*?E z+~blKyK{xi1>>=aKRXnpqDsa&Rh_Xi5XYDx4ftAqU%2*Py*ZG~&)7BfTRwN%ANQ}P zvcBKqrhPu9;>-C(vprQJ$BYIXd*UtM2Q=*Apeg}~W|t59Wv5Rc5LnnLkKt!IBrr2c z`ZjUfrJ=g@5x@q)j6w0!S$D*G$_M}BzKcCRP1G@U{@b|vem4-C5a^76?l*+D%&p2z< z46JoSgIVP^MqkM+8KJD9Qruqyv!bPa!?fgW%SHHLvOW$a_DZ*zyb!9%AU+WI3K&;=`3gY-8dw>0(V}+QpXt|MQGC+Ld3LvfZzdK>8UQD^vC)^haev_uE zPy#>5<^2dhs-xYkx!-%m!@Ium1oZ@NbFsOXnxDCMg+!dSH2m#Z6k;@!XT!PRwV+5#tgcXmGz~-G(kpyN znP5}yNJN^|oRDcM`)b<47;~GVigQ7Shz^% zU1s$STe#l<%#d$GkOrXwdQow6STW$tC2%hYXmY-Bhut_}4H)z}S)YVM5W+`64dOf&-cZAe+MCWQV+Pp8#)_}bi3kGjvZ)N9+kCYb~g=$p6vhEkdVM#SBk z#m(lEsuIr@9Bs=h=t&2a9)Ct5o0dyWVy~*|zt(TFeB$=7t=54}3<~;L__nP*6wNtN(*Mj6g z4E)p4nNAHp5u2dqw~aZuoe5^g6dNlv9&}U9Yf8_*Mm=KJaAG@X%M1aYiw+54w}WEG zMXrHl-6~IXns4~5td+%>Rjfs2^eYBRF>Z|LtJMYlZAY}Wbaf}5y<$(ccH$yWyaN>a zEK_v181-*wxf}J-Q$#`{$PUgGKEOTLQh-WooonrNC3VLlNwRlH0c727`)777D}QwM zAb0blvIrSObEE{!X8up=t_!bau(nNGGH}tSRda?iX4OGE10ULH$H!n<0b;fkjNjRG zGpvLxn=R}@-!)VBgx0vP?-5=thA=|rh{yTb9;CBal7Td{e@T6L!^K`h1AE`Lzs7&F zeJY{yXd-CuyFIR<*>Vv||QS&<__^vG0Y1JKW}+nz0jp0W!~d zXXTbPJW~wX#;utO#b)-{5J+$AlE5r~4U?p0PBcDI^1!uuObtHLT2W+7&MUowu&a*1 zY8Hm)eL6JpCnM7psBH@pBMHZdg%AhuZqUG(>j@R!3m_R&xZcN-tES__WxqSYvv0lU zJ@tuRfJn(yK}scNn9|}8Qk+4Lz$|q?{n=T+EksYk%{arSUV*tI5$wcvOJtF4DT$I6 zs!u)DUr*7EJfgko9?Lm&T`_rQo!Z!vVE7gRW95u#wJKRZ}!5M-bX> zj4PKFD6ziHAJ6Yq@`%ev%xT-)S9gv*&eosGo>q6_S#z15R`YS_=-KaqvFq!1EkZOj zden0o=LF5Wc^rw>hApeyn`OVCMH7V?>eSF==w`CbBFXlJCX-o z`mIncjU=PmCr%*nU{8vEj&o&(z`EB29E^0w+p)dc5-h>8slx z=qgI#meg*c(O|3t*Z2f1jY*d9o%vh=;(Nv?BAkCngv0CcGY%q!`ZosCXLT^}nB?ed z+8>xB(hH@}rL4B1;H3^f{u}PDSXH3~JvO_#x96#M-uNyVNzs)WD)aKuKI0sh@ z)#Vd+vGjjJ#BN?#VQCV{LxRkYgS6Y}5nN>7ZD!z+ieBT(m(b-**+%EU?F+m;5lS3| z{!R-S#fI}UaKWqKZO#`lecNm@wpd5NhNVIyaFF@R5p5)q65pxh9oIpKi-M! z?Na{4PE8Tfwj5+pYMAtA4d5ZBGkM)8wXy^Px!cOi&+OAl_RF=ehi3hbqGH=fk5=N> z+EyW^a3mDOd_xe#7OX5Wgcticy=m8~MhV#_Jws)%^5TSSWUiAnQ*%-Jx%0f2q3`cs z3J*=hBjg-K76D6otww0ZtAAdeYWYGh(1pd1*h87;^A4XY>F@213R}(m!&!WDh~LgE zZ?2w3us(g4ee!%n-z$lWe#OD5bL{|tqPAx8;oGJaa_yVu%CZN}CHTP_?#BlZZQ1#X zA#%T+9ec@KKYxx+DZ?g68jpGosmCHxrm6t)D$ph7_K+O*JK3zddQH`o(|42co*L=! z(dbna=L#T?(PKkJzGi=`cp3paed$}8km8l}jN}hJgL~I2xcL1trkD!sI@v>9nURtY z<+jFudXVs^@EUMy>}18h)2MeJ_Kb7>g!8HsCqK^v_058VQVL%iXRaro3hv_e2)|mIRPAFU$^~sCDSyX$9xJ#QP;h# zhVfXwnK%UBGWwGsE#+uaUL-<3d#VEuhXhWC+Rpg^Y1aqWHhE_I62Gg2GmImzIHw<_ z<@8BS@{{UxtDG0d@zFu5T!EFDiDU^(9j}pI;R;&1*wSx#w`ftfnYX@u`Y7PV7h8rC z)4*p@o5R*5TG!`QjnTLsp4c(6Gyy23FjuL_Q3!p3SbrKEA8pD1JdMNRROVGlpRmJ@ znTsU%_VnNT#dRZO%-%=Yt>c{QEg4_}UxMp3d)g%id3|KEWE#>1@t6 z^_mO=xK)bsI+Hu}5T;}}IdT%@C%jg-F=~^3zD$+w6@rAo`s|w(Sw`3XU~M%5rC9LL z0XS0IzJfd%|EPw3SC=AlD}=|)xWv9rN4I;Fw1VN4=_C3I?PcH7_uc@ZKL^vR2LbRnlG32O$(E; z_MlAGrDf&fYjb|vyKj){CuB6Bchlon4@AnO`I)im$-EZRrc#gA(QiJIp^RD01oRti zCxn}FY?aU*2a8)qkaH)TzY?gMpz7Q<*ikAvIS zQn0qal0CYga?~j@U(r{IkLxQlSn_WK z4h%i4Oel-#$e%XQgm;gv+mbsJ@}oO1cB(2J)dSym>^#>{+$89z!fB0s&qkP%^X9?o z>a&OBgV+^rIa!tQTPC}rV7nf%bMQ&u9m>5rcW2U0>JeTeQ!qBM#nOY6sedE;Ks`x2 zIsb&q_iT8NY*&4(Phnc(Q0P;Cu?%VZv--D|{HIQDPP3h{$uuBHzUBZ-$Hlj@ zgA`<}G^$qAr#ZXcp>G=K?$PJ48O6mWb%VT?&Dyk17XQ^)KEej5TPYZRbX43DbuoU5 z%@?$zy9sOYAbUX`*HAYb{COmE z=^#8TVp2N$PKuwG%rQ95XBf*^H#V43OCTXm78EwV7Ruo?5?}ilysUch>+7u-0}pt4 zc{`mPdFH%k@4BRvW-E#*GH@)PTkfR9+^v}xo7ye=&Z$gVZkrz!?N2#iv7wCBnN4bw zVx|xIblu>yrd{&OC+cdC)qcMG6-fFpC}KwZrDbCM$A8~{-{@BlyX&It>5lJ_ldrq) zWrkQJAST)*B-eHO!Q`-oqpc?nx&$Rsn%|@k3ezfIUEejeKXhn2Rtc92RJyCt`FX8P zVD)fJ{m;E&u>Ojd?#?=RKH{ZmPRQ-gggd`R-h}BNj+1+`lU$^v>OG2hw)*KbIbKk{ z+%E@~M2(lbq+A&v@kfXFiT*o{4#0{h#5xr)ij1b6O}}Mc_))^?Yw{|s^XpL%WJ+kR z!P&b}+|LIm9H>;iu2#t`%v<|)vBKYsSgFW(;#;_a-s};6&qsM;vsqPRLXC;N%{7%`%m+mMcx)LP+;FA%t;t>lm@yJO{XLO8An9 zneWzqNLd{(BxpRpOP^}jHsGME{t?e(Z@>+4Mg<0jp|-wIM}6JJXJH|qnU6&SVbbSe z$-8o5a_0s6!=wOs z9LCx)+JdUCT2iCsc+$p}Ne+UR0W%(naGe+N%%5be#wXjuWXi1^Z58{bCY@=@3YtYv zACi0LZ40@G#EbJ*OeFqC3|N20v7rL6gwB|JHQjC2o*PVa$n5v!)x{y6@Iq_$TrXmZfT^}0C1R7nyEPR+wCF$sZW@_(#EZK z8;NNU*1M7KmRg!QZ==lp?AiB0>tox?mlH}YfRxf^%LYNh?m>rU2G?k+Fhd4|I@?s@ zm);P%@}VE2!`+%&IqOTxw*1VoS8K|BhKMOP@=Go%S*qSAcjpn-ezp%%z`9NUBEvA5 zO^*A!y24>tS6I6E8Cs^gQI!^w_M=J4Bw)~1JX44Gj(?*-y>SSOJ*yJzmowBdmPgyx z%e<0j`>$h?T$#~%K40VPoQ}#N^UK!;pKgByumz4*8*tz}NCZ5)!#t;avzd0!lrr%1 z<*zq_TU`LIgJlFo@n;O7{NGrZ*Uj2@q;BcvxZ;J$v4$JQ;}y3IKqXxkv}QXma3&NB zr<2b;2bVZW*g~K_@vL9|My0nGC84s`AByhN;T~5 zVU{%{1SmV&I&ipQXZ%p~wkY6B*>1yzhn<|f{_UZu$4^l(c{6A`$aQqH*TZHJpT8p( zY>A8#iD5NdeN)}ccvqk4>;aoIvoQTs(vY+@$35$nI4#cGz*pbuA0F41gKH!F+F4L) zfucA3qR={d44;ET8N7N&5)7$as9~|Fe`{OW~-XBEqJWxqQhjr0bmxCxt67 z{a|w{>qgA%gJy4DCj#(8XNqMD!dHzUtMm(uj_}Opu7o_#l;ipEk*DL zi197Yaaq?pTKh6SXG6UPr`=lZ?wI|bd$FWjOBbYMGx)iA@U4B4v;WrSRs=s%t_%h} zt0>Jq=z2K=vb^qVJ=c-nR6*7HAjlRl^{IgsBzE$P$E5{YAZW)jb^4eWA+Lh;6 zY>|;W;|dNr-~=#PUJJBz?!<&gkUyUf|NDF(79SOvGT=Z?fWMd;1>D^lf01` zj*)E%YfF@aYiWJCoYW8TUncd#hWItRBQl~Kq({XeHy15eWX1*#+%Mz#0u#KlEV){2 ztfQ3c%Mt_nV5I{!hq)%3?lcFoU}56AAI|tE^uOrh)zO&dFy%{YPM2NUvbclyQvAzC z(i%lpSBY-E3gZ}IfLJ>7>40IaS-@t}2jX@1l{CGwECmFvVDq8t^+6-RfdK*U3f+HpO&-uA6MRIt&wO5Py*^%Z&1-+*4ap2p z!|W5}YGZ@rq>724rQf+9D`J;W-rHw=Yk~$v*PgOj2j-}S)5$y*8!Z9YGH#f%HwC0E6C^rAJ|p-rA#gAG1xPg zR&|-j2(8Zk5N?$y=z1xA@Iwzd#%zehwQ7(_K8L&?@s4@ zmmh0TA(8$dVoOnEub^b?bjMV$?{GhHEIVEy9?{%&Eu+xIi>b(gTXOAfk-^)G>{MAV zJ4Vvd!U<(FuDz|k(W2{+@V>(&7V71e{ysT~6Bv`*Y;uc{vD|0bhb@v&Nyb+}1(`GB z*_|5?3_KDpI{Js_ra7-|D5Yi|=onju^SeRn9`eFw4 zw!13Bo{j6%{z_g{Rd3A6%b!J_Utzt1_r=j2g6D)RVXW;VlRBx-9BLPa#Suy8IV&>< zK8FVmRBW4U$xym2syf%>ip+Ypd(4%^3AaM{@qF9k>~6^F)`5O>Yg zD?iEN^3~_2#PHXZRK=as6icQ}B*8MjftmW25yl>kGpn`U#tRW)tHbpV5NFJhcCzq; zpi3UU!>OlI&p$Drcp-f%mauqA^Tl8ye)|&!bwnM4=;!Ec6}y!$_P&rLkyZS*T(l8R zA^N^)bZGIUx@Px=1o)cPJF5I)s@@*#>m&)0)#(_yC;UEZ*>Ty~D(z5tDqhd5xH++( zw416XYFmaNRvUj!A7*;4kx!{ft}&>)pEgq5IkP7dhuXq!8M=Ih2aRNHGjqtXvAO3Z zD_Di)?i42cJMk4VE8bkW>8jRY6QQEV&?tZ<=(5PK>BK;1pN8{-557$wN@$=~ophWZ zT1wkqs}PAbq|&r5?$k6^mas3Ub@3rBWgDAz%vQsUs>H5Ddjz-M9H4YHey(?4T%1*8 z2CodA%W=_gH0&`1#rsH^XF+{rnBhY1J}wTl9BfXqqBgj6fEjz^AzEc!_X3xIOCZ!lcFFFctcfs_6|Gx5lX)iR3(&WsK2ybTR&FBbuM(xYN` z+vm{r9iWkhmxnIdaCN>>>g8pX760X-10!d= zgnpP`b#YvXaP*OKo`PfWcUU&$)A`&>b|8~}JZvKm9PfH=^2PEs$}(4D5I<_uG4+L? zW_C@z{;++7`dG@;-XWp9x>Ehp2JfCKCtIb+HM@Htskoj{xE=^xZMvwHCao;Y-$9%l zZB3dQLPc8N%KIJT0k=t6P{x1}TW!)J&86<-6!t!ZS&F&_R4P^*_?a%gULG*DCKLqR zYpGe7Fk>fEVVK*X^oPp#nNCl9sh7yeq}cE~(r;@P;r;=xxHjgRn{UTZ7k&!Xwv4ciR$7CC=8t=vO2%*Zqbpqlv&# zw2m)2UKmjNtZ`xB%OVTSQEUY~4?$6#^pSbMYyGs%jw``EPxU*u(?&#bkaqsNT@cTi} zS9u}BXPb|#VnNQSNn5kLnvj7eW1q&VRX{PVVdcq-T6&l@G+5>vw z{&iJFYYARqt6tujPpE8D=BFis^sC;R?G=HK*Ifdue+w& zh3I53uQ&Y1zt70=-~Q*&hFb1e=d=9?g514b{z30M$i-+8s>VxfnkDVk%Bu+A*@}qo zs>bWSl|)6yboTYI462#cvd{i+GzQZHFDa8zZ_dz5A`wR@*DjR$iaqIQi~njHu^WlF zrXX$6zBrP*-nG~I>ujMFZ5O$W_+vm!Y*!avFos1miaY*hxagbKxq-lRPLOJv4+cJ* zW;TsKJZL6)mb7HoA7t$RCvtc1w^!#8g|;)oW}SV(g~)}tIzn;6{p9r=NG1>2!Z*^fd$-!_}N0 zlQ)W95z8-wu3yB>KpsF_>mXk1ur4%Fokh&5TA)CJT8Gb^KhSRJj8ZkGH(CGZ+1%rf zqpwF6Pa^wO4YxS~fWQi-wH4X++& zNH_RD%~*+1iOg-G(Y6L3bkfQA?RPCtc7KVO+)!LaSGA2G(&j`7jywI&^92}UYAXND z`s7iW%p+Del4)loEHfzYZ3t$Xd@Iseo70Sfb3=BHGMoeD`Ib}6SEB(l8|cKsLZLK~ zNdJBIt=|ETLvQUyjZ@>Z-^bh1UN!@ElWl2y!V#o}+JRfU8|gj5L#4OLnJJwBD@R;* z2|fEn_^#!6J?)C%$3p!HVzA}PMB8A7$OPum(BNs;$K-v=M#3BYHzj`Kg}=zAV6$SV z>?L;gN~~S3Zhq*srXq~ue*G?%omv9N7hbfF^=Ij&!xLwvmJ&mrFg!B_DOB=2 z1PKcS+w09SM|4a%M*?T<<8T21=$HUc(u4#lye5$KgdDlX^vAS81+Q0IZ)CKk4?N+)wq->it;M+%eOC^5LvtC= z7~0X*ZPm43Ce3JO9X*HBww^a^`En#FQg1#v&uu=|xgV0Of2uV?7r+p7R#@{!J6(n4 zx)n8yN#DrST5MRNY*JejWll@vhp+H&&0b%;I;klX30S7BVpz0YZ1g=+->SChjz!(z z)dO5E2j1^HOz$Rm9eaO(!g*XJ1SW9XQ z8<%U>E6$i|k08O_TvZDZfz0`c-)BeP>D`bSf}VU%GfXlO8ToeA!zXX`$=jPRQC0cU zh|8_gn$MmIwmPCN@_T_WQ8N)QioDj%5NSBG5yL5OaZvicRHD`CjmB2z(^;?kN{ut2 zh#dnk$4Y#QYV4tl8Tj?DJL7%AO+|2nIN(iW%&hTL^ZG)=FBFJm>bV$r`UZO00X1$q z94~pt3A;jkezp(0J#*w4M8td~|HuGRMg+`7`NRz5?Gy7hjXfR#SzWW#GDVLPdWQsy zc9>20!`>67-?E#M*}0CRA$zVjat*5&zz&6Ab(HNN?A@zFAT4KD)sweXtfBiP9nb3YK z!M(bej*ZAjkA1k>^KQ`Zp%W*!(Tt#V8aflnx9~`Et5j%k&Sut;4Z+?r;3a_Ewc?QN zYjxE2vv)`@KY3|165Czvr|?OyhM&*0xKjO5AYNE2A@YXZp5JpV5M@iPy9>ze@O^{^ixacHpn( zXI)Q1O+siVN`51^Y|E7U=j05k9uqnr_HCPlP#OK;g%Bs}%(x}*Q}qNRSO z_Mm;MExcyJ%g`8i0+468VBGmF?2kKqIBDv?%lHg>&egGp_`J%cpx`0?9c1|V?_8)? zfy&LIYNXC`aRTIuo5L4ojH_-r})^eUU9>S<-Fwp5GQlf`_3bxru zH?w$PxSqMMGYI>A7di#rW&#}-zZM3R2gFE>M(bVRveYnsBlQ+3p~-@7haJ{K) z{3@U_?lH@Ehiy38Mmtp5b)PAJMf(6&ND3NSKgD6?Y^Ns-9(D zUz+IViFJ&g4??Y80JMwXhrRg->)Gmj-1+bn&1~Q=ke}oI|Gfd~COBWCgX#U$Iy{-@ z2TO!h$Zb|by_sz@QCjg(7)@1N-%6{Fqt!Vd2UngHVnpI;{upQYTePk)v;LvgWo-r~ zk@i%x&ih;yajg2U=>(PM;L}E^*Nru{1_d--w{f&smgzs*swAs9rn5coX1&16Rjv}R zJ-;}_8^r>%?l4GvfGJ>u%0O!{7jF1G+~5{)}Br-9Wvtqk1LzXjhlXKd9DoJohf#`9MHwkgm>Q?38Bj^Kv z_k^0;#iA}Vfm-HAZOZx0Rxhk#Js?E<;?zdv6&`ulDi^Dn>|e4|N2!-qhGX><6O&)K z7kFW09*pzR^XW?7b0;RItNZUF&i$CIuG#s#t)ePlaUjjmQHc}rM!k7G}RTPC@X^-=LKHAGs`ls#7~MlEye z^IJjV*4`u;=j4bDyK6J4*fm|au1t`9kJ}PFrG~ICLM1J_Xj;PHFzjIcDk%?8(G*N( zR5uaGu}tPn{`3g?t{H)e1~;(9inu{iq>J*CpkZZic|ki@Z|2+WZM-KMFzm5iOZc7v zX+f|&V5jM4)3Qg1$7?0WZ~A1GWiO2!%u`w5i;A`JCzuTq>MEI`w6&6$&h9J9B3R8e9y)tRPSUjlA-+GhksO^fS*XY~L$o3{L_CNJk@)Ol5y?qV% z<~CXaj!EUE2+--)WsNwG#=EG7D7CXn54!Hf>?kX#(EZKh%-eMMQq%Ils5^|z1oU}n zpWH`P98Mvl$kiy?%WkjWSezgd&8sNw#>_gI*`8&--i(YEQ$$9KJNA4n)%B(SmtF@6 zVBqpQ|AnFn|CCb7bhm?*=F>;Dv3RlGOn37~_k)#ZCLh}_KZ`mm>gp0Sp1s)dF>b?g zw!Hv-MX5$xQJZci%THpLKvO6C5h2s4ARyZ{eaTGZY|+k4%};+x>QQ#fz`Lzmh)RE1 z;Bu36YxZ&Ad4{4{!D^(g3q#=gnAv7PAa+tSSCA4wg7t9xspxN89t9^w`;=AiwlwPKPK{Sxk5U*BS=1 z|3Kx80+r;yA2C$8ief!=t0-(8U@+Y&KUfAiDi`Z}(}gU5J1Bn=on$zbAB^zl-}(72 zM2xKncR@^Z3(1##lG7z%OgvWNkqH{fnR(USazeo&nQ^SS2g|)*h(PDnpy3S#j4nwwX{y46J>q-$OYhpr<%wQK=4rlsTr{q^| z*KjH=I6uecHvU-+Y&PwYae}&Ou0z+fI30t7ODYK+6;~#XTyZXzvBrb%G{` z#TLDTnhsF;<|H&0vGN)N=@aR5FPoa91xYR}KW2x>kkGO;Xy;tt$3IHp{#bF(DeUBjB6v@`Y;&?h`2!cX?5=oGlYo zr6SfSIGS!WkFC!;wgaB$|2nv~Gs0mw12SiRI*O7w>p~d`uuA>p2wYAYKlE)Py|p@f z4UO{X8okM+fb(pN15h_b^Y~T~_h{p2s9z7LP|3ty36z%iPsBxX5lu|?0DStPDMOfW zg=KS}x}kgL^SOvoH}a5 zO0pBD2%x7{5WPm*Vahc$;8^LhJ;JC}tLx%a&7Guz%_Gi-o5=##bZIT@4hIkD<}{Od zDg;TZ`2pSn`DK1f*>5XOd&tzxe3H*dNt!0n=`WT+Dc|on0|9Ee$VlV@2EPztBQAF` zftrtvgO`ni*I8kB@{ z7>MbU0p_GD}RTxXxZ2WeiM+y6!#6S7V!+d%R#@5XLpR9h@kM;gcIjB`^(F5qO^_ z5X)7p$0a>+8YkZV=L8Ka^9lPA`Oso#)*tUcKd9G46Qh>D^vz^lgDm0h&OtnRfBt$ctMk>d^j{l zy2Hdq?2ZPVo6+d4?Oqb>JVsPbW=|s>U^BQ`lwQYZ^^o5`1Y{Z2@C&BZT57`8_SA3V zj1)yrlH1pH_C3>-Opwdv4t^|OMK7*ZKdkHr1}Z+HRq-lFiSQ~|*5t4r_#pjR_fsv% zjFaBPJi#MZhiHCL&uoeo{!PeQc>RrGu z6O?lnzeu8*`(YK*p))NIaO_nX@V6l6UF)gc?MsQ%`n7r@8QCXpn<1PFShw2NOL7R& z6#8RB=X{qS`B5KZXs_NA&D-gz{qBt(5CVULY_*y>k~%Lj=d|bffXwnTG6iq@Y!&%6zq|ABDRjy}W;lVd!#R=C7h!PzK7EE=+Bv&zd+zT9Xf{_;qRt`1xAosmHg?rxX`J5P@ZlPdh- z;&2H=^y%$-VqIxIWU)j&Z&1q0cq?KGDlD#qUV?NNtgOH8kZrGmw@8 z#lCoDSk|-$9;yyQ*ndgz*d@c`?0^ZrRg?B!gw0`bEYmLihyW{#t9vdjx;Su>uA>gj%D45b=Zi(U_fglWW3Pch%F7cxhfuj&I6|f{Vq|GH5LCX!`!+!}eYe zQ_`4v)B;j3pbs3o{c*zi3Hl2itS=2T2pA%L&T-7CwL*}bMs%?JcJSQV>`7cWFI)5B zeJ|>21|Z;;{o-ba_Dq$%?s=rOA%kV!dewN%O8w=PzPX{aV>6of;fIyctQdf6Zrjci z=<7@xmRxVQ6Vy6NtzT5SJ=wVZm64K*Qkm5=<#@D1PQEpHL~g&BN5;jv!GoTo3e ztQ>gUKb-9J3_04BBOrR#>uY|}r8MDOyq^?IvzXcN7$kw$9wGRJ1|3zSYA$e2vxTFr zc>h^enne-?BX8O9)?(PQ$07)8WhqYG@a1)Dpp%8%`MA<*glvbO)g4$s_pZ)_$qi%3 zAMVxAAFtQ`2r}ozo6(Gc6D_i(IPTGa>f`#@d69#}Y*(7#?T6v-u$EcweH`M;B5w!b zLBHSA~8S2TN7d0uJb4l8k+`YK*RBZ5x) zIvdRvzcRbT0D}MtHKh`X(;9HM7yz6m8)Lu~qq}9p&O;U}F$eTisjB6}2~?9Ql{{jx z$8C`A4~xn%L*jr9qiWya%O`UnGLPEB`--b#5_gZ@Ju5}-DE?F?(Zy6PMnJm&QS}?C zj@q3Y!UYWHnxxIL08s1Y(^$KM0sJqQ*3cVoAnv&=E`j;VB%jJ ze`w&ks&sI166=!`5idjRn=c7p;<&NG(B#!mCg#Iz+{RTMjBMM9$I;EJ&nA`}UcSbg zO8H7#ztnCa*8WF;D9~7U_I|BYlSe0%km#rxj-11ES<{HpfbGn~I0a;!`RFPt&Ux}J zsm4y${}|8-Zw|+12b^b^yK4pA>b4C9y|k$+Y9+V)Z$G!HKPS;XZ{D~``Ww8xX~{XO zF>40X=Ed$eyB-aO(*ISv}WTi2*{x#LEQ_k4CMl;ROx)oP=6L9(72a?sm{Rt7IS(#1T7S?J~e+-4=@1? zxoGvc?!vrvf6744$iO76$YNfXQKizwf7kt+0lD-KG1_$w=1Kp2JW#Ifx*RrfH>^__ z`dH2(C+>#a@g!1KC*(Hh%+ToC;M8pqK!f4}Ygr(@BPLyY6Qnb5HFEo31wl-@xDxKe zi1kko0RfU%xv^o69Q3=`6ugng@s$6cShWm~u2Tn!cHZxM-W}O>tK*f7`^p{RX1s~z zwRLrYQ%nT@srRdY!JnNs79JWr-cI$z5C+{j28p8-_E|cxE8`iiH_c3C7dPx*lSw0v z8pciD0%466QIn#Ohbc}lI~RrO?ZL~xa!I~3D1>+=hQ`P={@dHFNpIxx$=+6Z7#aL5 z_IMUuaIlg{HSn)0>X4#J`SY;k?=XE9#cL+{FJt0{bt)f`EZr#QYEuUv- zy^ccr=k9fBU%bHR?e)CXzNSl>+N%Y;N$~l&4v%q*mH#lF-*$ zUAD_&AF?L}3)l`U`*aoHSx@>s|2M=EQry2mmzC?TKwcK02J`H{zbk`DS zqs;ZGt2yT$-(~t96{^g^!AGmGjgC%?Zxs?BTXO3&3@pTw&(i93SS-wcWh1uZ;}jsn z;`jqzux5OW$-u3+R-kpF-sXu2U?8bYRYi_xK}n8CE9!m5#I5{l^YnLGC+@j*%U5|H zrxw+TDyKd1aLvU2S4k?>KL{Jfq%vYs*e*)7*>iGl$3dx@Vd;48bWnnKO316iCC7Os z9BlO(IckjDZ@_&Wzj`$L;J*Iki-qTGf8$pd|1h5GjL-7B-V7kfX<)3lsW%#D@ckYJdnKY{y^WBCUo1+a(AlA#vo|GsM7q!EX$l0(IdU9 zouiUb%)(o*4y->-9dkx3H)14uT(lR2ACW~An(IfDbBUE9g$S>~3#LDGapBSUop!h{ z;~YGN_@-18lzWn!!v-jR<0AZ^Qsn+r8u|ci`nAIQvx7OY=y}c}R&6j7ojK(Uu_~@m zUWdxsYmd9oPHo*t%Bqf37h!rp#cejUTwsvzHg9O?UGc{W9=GdM!iAFAK1K*sUfxze z?%_#1Yn+NFmjKnwPl!}OikgRQLEkOG-ZZ&D>Z9D$US(IZo^-=zD%EYFK!hO%r1)Tx z_W56yUxG$KaZelqZg65{#n5wm9KJ{`f*OtaRN;h>m*#I%gsq>~mi&>Mr7}kl|G+4^ z;r(k<=-;Ngfi`?xn@P*Ut;(;-W=+oObsi`A6Wk5ofv7-$(9C(|w_BXw{z_H>i7lmi z#6iMkw#L4ActUt7N&K%2UQTTz3VeW%QGu~yOsSQ4?lbc!0!#V&kGmRK#Jv(|ISKF6 zFV?k9PdS>p%?ZaQ&KNQ_m=kCR#HBvmoCq$MXYG-EA~-_yKsTM5)W8JHvlScTgVq;* z=|rbrgmZ$FUpkFDL`zk&#q%E4MRTP9r$~V`0A*gK6j0o2w)#V@GdIM8e4FC}aXj*& zZlk$IVs(-Oqi;=#eq7~$UC)4)XT_7>6uhw=dknuN)1?8wZ@914$Nyy6z21=+72j}N z{UpCKf^@ns7YlRqjP51&!p^mhR^-|mo93f9zm-?EHt|&Xh3@{=vnK5qza-&W*FS{>iL`$7<>zJu&IDh9$!Ul=6d7eF^HJ&96X1odU$aI z)*q=^n^I8cJ8pJL3lqf^rZLs+ha0>=Pb^<-w2|qR32KYa9Z$wm-TYFz5_k9s$k9&n z;U^!7NdJUHAO1V1a`#Bio=yG~pG_5SPUbEJEpFi>&I9`Qpp@P2$f8m`r{_&OFM`^DYplu7+OuW)h zrh@=H9fu!^+o7xDU;1!yx}#SXljVAGj`jUf#86%M{?s9=0Q?qv9Cdnb`!gIxQVI0UrawEZx;~AkhIHpS6A78v4cy4K+W6H>t<(^TPx{jeUrn;P=ym&*P z3arMlOd2TpZFn^DonW_aY|_FVZa5*mIKdWtM~W*Bl>+9l5BMXW#>!YD+?G1H94~D3 zSa9Q$OIF+2^0)kQ!I?t)_N8ZW9qEKnq_;4uA|0UOP^%~4XwZ;7D=ay4=)PBXsg9^T z69`Jgu0fviXaj+EMH&0)nst?d?0?dkC*ytAy>ZVt(<3dKAVGX_`Hk9g2etQ89VV{= zDgkgIhW~6H~WXn8kkyFK8FFlgHvF%`^6*T-2`DXD~wjdck4RU0+7tGOV75WK3L1^p1vJ?KL~U%L~J_K4et^x5yqDlPGm zAw!sVWxt5Dv3&4R#$rF7zqR_wa5SRG%q4t`?%Wyt>cV*(JSrf|<=~qVDU1G_nhSxE%viAyt<-sr(5Ec)SvS9$TQyP zhjKBe2{(5K+mj96wR;^`??S2mHaB37o;gD%eOK>nu*-jULCdEN$_gYHn{{s4DJ;}8{T(j`&~GS~jrwCP}42>5IWMv#Z~ZUBlXj`Cjw`MNV0?UdA?KjpW1G z+f~xKZIebw&DeVF*0|DbIIx&5Yb$G_@%{YFKah&8My!K1#HJm9I$c52<lg(BtV#k)1 zrh4VQ^m7&wsk5ZgZOGa5*(wLXMf;;;#$N9jj5!#YgC5e*#mBU|$=Kcr5`On;v1PjJ zkwk+|-h|G(IkIW>5|NbXxF;>+&zjfEI2t89b)_0oQWHm)|}TpcamvOD2DIZ3K~N(ULGvq&*DzjsPnC6);{=tOYdCfw^Rrg&ri(8@*jUr z5!9X7Kw}4`hC~1O84tj9<-N5YL(uR;+04NfsYUE_@h8qhh=%1ZREWv7Au(MM%f_p! z$H%WG+0wY#Ww{NU?k^PHa8ByRm4)#B6G_;AKiXcF4!6FWtCHCGj~!=XIk1AdQL$*G z@h>K{XGyg*Qa?&V^F4Kv4hIh_9ieD?vYkxKSk|r&H;7_NjoNAmm#((yq)m{D4Ws@! z5iY=;FMsaF609@VD5BEow(VfC_u^1RKbI*n?EqsWe56*=hm*QyRx(piu&MARHz_uc z0!uAhrj2C^bgZRvE_gD~t4M)1NL;*YfD8tY$!JD)ngSDA=LiOuY_KY4e?oYdK;lV;|z;AVTve7+v660|YV)FK3Rzoj+GCfb@lN-uZRCy@_OYNcb zd9gZQq(64QUFu5mRC?_M+O$_$mWE0$EaOC8#VaHtn!*!b zw$nD>@8&e@ugww$^TeCXHCM7l2EJ-voQmR z`}u4k9L65}f*wYHJ>OvVK7ZWjb6hRgl zXE-gi!xN?S%{f!rC$S5sH)lx>g1wRvCU`179>5_Hbo2ujug~I>;eyBV2K%ZL4#}fe zalYKUN93`*#ALLZ;Y8K>Eaag#B{8SrmlZl)BBj~*)KgwjF!{<~bwD`1f{!JhU(s3N znkgH$EUog^NRoT3jQE;F8}di)&>ZF3f=F0J9_? zdNo)y$>d5E6(Fzg2Vhv}_()p22XO2w@{A|DIJi<4^=N38GSl|zrMoPaof`*2jS;D; z0hzrgnqHml`U1cGxZAhl^=|JKXbmF^b7>oV@3ej1#=tnji+uOt z;n_m-^W$cFo?3Gwv5}}#7*i{>JfaXU=CxlPn<14xKSI+pl%7bo{R$Snv3E}0A*IPw!<)tO|8-mJR;VD;+(R%M_ zMAy^9R=ASFOY$r-A?;IZ#nXYQ9wHC)8bz7ne(35GB!cighzA2VRzKG|Odh;#jDUKV zs@Dg3cF0<-#b+yvEp>+}a6Bn_5n_Kq`w@e97BD#=)ZiUioalM?T)_O!bLFWwUm(G; zZyn54w(vyqPcJ49XCOv6oE>i;!EmajqKYL|tjQ2eX2}c2vw0VL6xkA^l}IGX{Nu^! zOs4ugG~OUbLdd4`Dsbtx_M@XUv`~ zt^KaV+Ymg(C)q9yF~^$!LNpX@{uCA4q@OL@2M zteLfQ<&wz04xqE(>7+9|^ZDHs-O%%Yn0MBEbRT+@|IxS;!3~5Qa^EMHDGfxC$+<^Lg0T*eZO-EUC2+b6Y|7#$qPwdee z&#&s$V1-WLbmBmL17E8Xz|p0osTtBL8-SN$p8>#D4)l06!|3?oM`_;0O$s1{MP}dzl}MJ| zg>U|xM|rd{aNcc(J0dLCj1MtX;L?3$h%`x8DaA);U?!wG1=9#aaK=EdC^gTtUev1F z2Ax78%3qD<)O;5E-lZhuc}VK-7g4@x6fe_ZPqkX!p4rnj7NT{0Re$nqSM3BCDP>2Bqi{G9*{rkr?@TFWkorE42TP)J^e=R0z~w zv8iywp+&a&@FQjp?@rM^0T@ufLelAl0=#MWDMLYJ`)8zD?d<}GsZGVGK7;WI4E&HM z-|xQzxR-nBJ*~`Q=1DU4Y~S5e<>O8tRg9&5-}{;C-!*ePc0iP1o;z|`+$fWV#kLQX zgG1m)--Sc{ho=sU928vFATQb08Yw@#E#rZ804Z(%@YoCTZfn`?Td=@%cQfcWGXW{a)@uXEG-|r!Vbc* z{Zf-5oE3q+gTdM}@{sLS6YcClUh9nGCt1S5JEYYU(hp%I8<5z^2!M;w?=Jw9ChdW2 zP`)SsfSn3Qe_af6{5RJn>YGlJvKtpZ2onX2WKVgeRyafkcMRI{nbHK z`@xk#%G6SlA2YMxyBB3S+spnUICG?&7H9ZNQb5Ugdl|=y4(O9QSIG4B310n;V3{4N zSysO&nqLi8t7G|jXtnO%SuIqe5U5i2#u+QTdGJFM*#t1o|4ZPU1(s3+E0EbLGe5LZV>Yv zh`EA2#NT&iRFy0UJMbK+Xd@Cd+x8;G&v_-+tT^&cCgyVXREGKRE zkks{*x7(wOw<@(|F|irdKI?-Il6>7okmE}=u{5G1H9v+m(hc-uMC1v-UWQbs#wJA; zyRk@T(dN2!Mw5i38|gTV9p9-6204?V&%pI(8=k$MxINyVpDpgGyrXzK_XqAq5sp`L z<<7HSSr!jDomG!+y>GOp*+5vFmr#Uet$XST{Yl8HkH~W1z6VA(gwrHnNg6Q4ieP%| z6rgmI{d@(eTUV1)E6w4%ZaBCLdcWcLncw2-?Sw2f9KKoz#!C7TJZF7N+{*AgW3ulz zcr=|({9Ah8zkj)bFI({EiP^2vb7M5T-&*}pa^I-Vj*Z(6rbyZhC)SQKJziu;bs9)l@`~v%xghJcDQ$=@ho> zdqU<)>@F5m(G?b2vQwS2&`>Gem=8@^w=OJt>2otLGPM^cX2*qJ`Ie^@)@6+;GMg<; zx(Zlbx#a#<7WsbNXk5)NH|h?(Q|V)L3GsMMLNW|7U(QS9TFU${Hm$5O2DMhVR3|Js zPA)1NkFgsY<&_n|=cth84^wKRrKIzbQE(E-2s&L=-E;`g7;WWtRXIuJz#s4%(y@Vh zzS0RVB47xDhIej6GolMsCz;sh)Z@;mr7#e$--$AxtP-_`ExNir4OsSL@dhLp==^+u z8+vF=YWChx_{PBv${!K{mgZI3kIZDhFrxK)P@zGa-U|oayWB5Ex?I-4P0e1 zZ`p>vE3feF7XIebyvTi2G-xof*?{78moykJVM4KzrZ+XCuXeZM_gIZx-Y$lVf(QdX zNSDWkD#tCmhV>yA$8S&r+(%+mUUQ8agC6?Moh<(LcG3fGju`(aabO?f7`s$mH(}5S z5CWG3vnehgytBw~bLSe0$(Yh;Chf8Vg6c9C$q9}`isV&*BdE5;?d36@i?XgP+za=hF> zG;TMuWG&V@4_n;7wEneSew*NYD&2D#ZsH#l**yyMQ>Xtba(m*z91pR^Z4joG(M=gg z)3)6{%Hh5xv8T$mioaFu8>9cP@g-~GEtblR_ZyApLM;QccoxO18ZXiLCD41zYydCh z?cuz*k!Twt28nMu{H-Cryi(J5`f%}!g<7F#d+Fcmy(J1&KB(9(i`&uQW=Q)BHo!!! z#S&O_@w0Q_gFgQ`t&Gu8bii)Q$sb|aVpl86q028y_x$40bJF{KNuRhB@?juI-LnL9 zFCr!5uD8rDcp(Gb4FvQ5<%#FZgD|ZOjnS?0*lMZKxxJ{fPP=p+WeR#_4!som*YPqap#Yd)2IB941zw>0)Yr`R6?P4>qD_+X??8Byu{fbCMrQC3g^Em zm5nRegnuqzC`xMqUvn+V&;GZNp&0%o5mpu1_Qu_33ust3z~8Li<%cX3`$}8_ugDn( z$iK^1Y1&e9oB(vYQx0K!FpS-Zn9$V1-c0-wPQTB;*hAQV6Q)i8Pe(Xx{px0eSFiX- zBKesTJaltx17P0jXV#jr>JLb?s%MMatoT|(Z#oSvLO&e8`K^=-JSOa%$LXGd)VoT4 zm{xm!skF{r_G@fwH!oc?u)IuHh_K%_6|n<^sB49-w}!^5qaw4EERoUc{g2v&)5Y@L z67CglIo}2LkWdQLhh#BwdG&Z+!gQcnA+i6>suBmRhMj{yj zCl0VUQByvdt?la<7m_Bh5aQ|RM{u|t#n9rkv}?{@28K+CM$xM*uB{NlHBB?O={p?? zrTbO_aj}0>-JIem{4L|V;YWIAd@o8aRL=K0kDx`-JpNx@YHH2wOswHUcGapm+z8d$ zpleebDESCvR)K7&;Qpu3&HGWo`MtO5V)lq#BGbKdVeLw-xP*7-m6CW8>h^cJsnduE z`_k795~ZsOyxK@XoJ1KnwXSn!qg2Qu=d1}+zSa3Jc0J4fnzTW_pTw^4klg#!Sf$p} ze*Z5g1!`M<#EEoBRq#ex=>|>x1c&uh`grYx8=1|&e=0BXGjr`5uYjKiqCk(8As?8^ zNu&2gDG}z>!y;T3fpsb8J@sI~y>r%W&1^Rf9cFuqplv-w1rV8lcaV8G1zf9ed9B4`~~;>#zoBOL-1NU)00 zBaU>KpxJoyKG44JGvx10Ojo)qA0D!jAx}l!w1LSe35FKUYpMjMwQ}Ufe7uqr(I!I2 zn4t5L(R^wS>Xg}66j;&2E#G)=l{O@Cv+aD;YK&FxI98=~{`u@3lYow2dB`y;+d)@CQ>y=1>rElkt(PK=WiUZX|0@&Rnci>=jB)};j! z9z$x&F3dHenj)es+#ecat3~>byAmiIo3l#A0tp#XL)^Sy9X8U{AEc-#y%exrx%&!I zoWTs6=%s}%eR2uxdC$ojH(gQ`*drQyTVJK<5C8NC+xN@$$a}+!b~hcKpNl_2^}DS} z$7>DjrxHwgQ+iLMWgyM5V_n8BERNCnT}azMkkH}^1>jDW)G2&zVdJ_Y6}NIlL&L)A zw>(-N1xu8F_cG}_ww`3&Vlp> ze$`(<^iw2=E~a#qaR*szn_K1tft?ohAIn9#1$uT*AxD_17qlC7U`z5EzoClA(?ig+ z_?Et{|7MbknUXRuWq~M?xIkoOR|Kv#GkHV%jnS@!u&>hRD*CSpmgoetf$)w7U@EK& z*??nADQ(L7oRvCBYt7;?m0wy`AQ>zDqK40!qP;v!*;9SN*Y_3%8tlzH6gIFxVa6p) z5L?+R7F!*^N)afvKgQ(U_1mfQZ8+WGq8sGo^tMz`1+MWc%q_84Ys^EamJtp2v|b4# zBUN5lHqD1MLz4B~lOtBI$pF(_v6s|ts+mh8WISNzAU_FNc9!$jTuHq4=KId2qu-FixYFSD zQjz+}=WPi|+)&yaJF>`J7hY44CY!+N-&-T{uMyX5de}miSpHpK1GIMzegekt)K&{N z4L+vO4K6vsFI=)Vs^rN4ldt&svC>Y%BG@eweQ5|(n|1gYwy}3m3t_K#uBT^5lgVHV`YlGx$5Y|*d}yla?%Y#e(vY_4Pf{c$PXx=8hQH|5~- zEI4j_G*;SedI?V6$YXD1#*-I;C->cMomQ{?Ypmg*c$zggJul^gzeEK_LLn%gZh`7d z?b8x0|At*=`>U8g8GJfHG-jA2lz>vI-Px7)ORZt_TSzKdG; zV`KcXLYEqtwYXbv+*-H148y4u!mlgkSHBy|d>FutZFVQAiAwdFC4`9GwOdFHA^S}N)_rWzK$}>4z zxEJmT)OAa`rVV&2lO2}~2a1rYtCRp<9fIl_*=fS(LfphBxbBT#;QJ%THRQd}lZ*L} zRUK256F#4=pcsi0+}4uScI$I#DxPzTo%EuDYiLy(tF>21)eB}pEqSeYJ$d|Z;|ku5 z=LG&>`||R41>WRKJXip+E`e}a=5Vi%F~L1P?tyhwKUHC7WAR_s2c6@9lD_+O+6Akk zPZ@HFpK$LZhLrjrxstVm{pNnlY2q3*gx{G$vg$M+h~95r3MpAB%%KMrC&br>xc+UQ zdPxQ;AdR0O`*xr1vpK&pzT>YGi2RoK!sgg`VPfr>-ZZo|y{nBVV#;#FDZU)Q1nbRX zqLeIVrJFq6^9o482Wl2&Go3q2FYZkj^c){W zi!OI)Jzg%>qDeR9_^joJM#s^mJv>d<44c?^#sfnwR5$@WNLmh{B z(o3bE(kEd?|I*?i&jqK5bt2QkLR_%&-@zblm#Y$dQd(SbeX2dSfSzB`4iscs4G)RS z_dB+B{{5Tv#&FQpZ}tnOp%b*K{OPJOFp(Q8(+;@goC)ZXqH2D@Hr^%0~+_#I<)PwT{X>!H-%x!IezDOe6 zyPqTNpZ<}ZX+NxexAAl($ExY&TlcY%m6prWN``h^qsil5ePS~?Vud7L4e-_X{L1-d zgaKGDXMPl)VqLZNtdn}(hA@rUeTD+V?LWE>d^*_p;g`R&%4Fy}4m&#!v`OGjydTXp zmeR(weZ$xsjItzEZ%oWnjW!*OlO=!ZDtJRqxmTNVF5hNvHta2kKI#&yyfJh7_vg2% zcWo{H`@MqY2l&SuxRJ_Gj7c=NRsNc^UZA}*V>xPg_i?I6JO|83WYQj43JDS0@+Iah zq{+q3+Q{Y(;q=e$X+2?Yk`1*xz3VUGJ=)4%)kY{?V6)la;bymK2MOIJCog(21)ojL z61UepNoHIJX&-+&d{Fq7SrX=esNj zV*MbvEu`vKc%blDWjQ8CeEOBuUo!KgO3~eM4+WYY1cmf}d)=n!u-8iZbdWw~#k6Fo z#v?XO*the~wmGXy3pc+)qguB>vE`v8(x4cjkGEnC|L ztf*MifsOP4faqPVf7M=fHdYVp1uKP<)!ucI>${UjwqE$5u#NXeoSSNUr+MOk=wck& z+6tRU#Iy3sz@xWEjGbYf;bu43yI_cB+k+LtY3KzFqY;ip%>f<$CWWthIbO$fjGB4I z_8HP%Cp|I+S)M0*cFD)La8(}DLQXA+C$*(w0^RPy326U4k0^&p^~cGHkvo4WPJZo< zEPVg3j1Ova8%`@J?Ms6Wq)GZWgXI-R-cMie3GXN}?6&^2{fI%VILU_9_m1nc(T`~7 z{u*rw){F(^D^I`lc>q*_N0=Hwj%-C`<+*XCr6K5c!(np=mH*)O^ewum7C{hiGjuBD z$cWTBVK`A2pnsHJPa2vF4s@P#73so=QO$DADZICNIBuNcT+s;+);<+6+h=<+3BHxc zGU9B9`pxkmsxcLp%i#e;?F-;wXGy>+!Td9;<7ghKEPJk@`_VmpU^ii??|tLy=mX2k zA~{nYC=!i&*DxQ|z`D0Ro5)!AxtRsBt`85B@*9+jR%EXVA3rCfxIA4z`3ZSW>{Z36^$c2)LWkB%xsTADTo zls3nye0HW^Y)>@SVCznvD^KzoE3bByd##GbiBnWmSUsHPsHgO9-LtLpoZO$V_`2|h zwAI|X7`D15Rd@mv5%GXu{Ts)uZJ6EvfwXmgtmuIrH{fZDMAF#e^cVTs_33T1Fr zE_oSX(S%+96gIv><)0yT9F0q`so<0JUbewiuk}kk&?EhGmU;B}?91Pm8YMB|;;KQ* zPk0GK(0CgUuk)6_z5g+3jY1B-a91X9|0HabTg8Yj)^~T+D(owGzf!W;357PmsdY`2 zhV7}c_$CLuKcPxmV=IApOEbO?MtMjFPm zSm}6pHABu*v#Jyj@?YKz&iJa6?HS7-y&X5q5%)9oO3hLc^Yp!Vx2Mtmdt<1B3Wsbj zh()T5@FU;|EQf6xW<+a>4v%2JFN7$M?lVj+DLe_$s|JPG7<( zyZfY(dw^8_r%IQcU1utbBqnpx9P(Lw&k(Qj?D2!8%+FdIU@9OKyKC8gpMw+ic2d@i zP?Bep!!3UFw%7oq**RlOJk%#0BKY!VuIcw4PEd}khR|7u>9>E~At%xhb^{M`X3vxE zlFoi}h<|y4RnV-f9&)F@$!NYn|9Y4!G@?uCdgW#VcH`+!`p*`z^4!n4uY5#RaB#RK zYEFayw4xqFxNxC$IMXHV&h~Vm4d;>X6~w?jUgVLnBCeF4xzX8xM+SOrxhw-Sf~*N~ zUcKGX8;s00Hca?2952=?T7lGs>1bOBDHf;@EC7tOTz7X z=O9iHfn)I4{hW;ULL2rUFCE_3ov;TZJkK3}9+v5@r*?-OlJ^-{Nm-$him!6mayo!- zPkA|L8#DOa^7x@u{&qHe_4+h|VWza(?DBEK<>zGl>x~hu-}=CGB}hi`to+OU{9^|- z@f()>SE%V(sFHjcb?*#yJbZGB?O|4CQcT4xwF{|Ch0E&ja=d$It?g$kBIxHxA7~#K zc0W4bvj7#JPcCVsk*9=TMo66S4~^lZ=70TZv!QA7zdD^ikEA^dM$m{^#r%l(nRNMN zXbei%R)QYBbjfuG@*Ew4m4~GDrudg5WwujeVOK+y%qOZvzf>}8gQ>XuS#|xE+`mAD z-_3?r{t%g3{9m@}f|8)c4elgCR2y66*C2riB6LZe1O?Dg(!{!lx`m=?@@R6tXF%FN zf~z*4`8|KE7qBpCoWmWMS-mO=v2ewxQkyMt@mX9^z0YYp_Vxfo%WcJf9?A(W>8z*M zd+G9ggJ@WP`1{ABJL8#=jez}^Ek%jN$rI;2E|_R5PWEDV03I*ol{WU*$q)<}A4T%jv|Fe=ID6@fl>b-NUyn&hq)Kl-L0yq}^Oyt5j- zv$AFlx|O#Z#hg20MlrJ11C_n4oXrOKK{MO#So1@OwfZ6E1G#&>EBPrI{QN<94mTju z^6%V@F7N zv*bJbV3=(D6QQ7+j^B%2j?1_q5yOI!#p0L;GDa2CS%;Gwt6G6GT{yM&X?tiumi_mM zU%|!$C?}B(s?qM&*7tpxwSu;RJXt`|najO*+{tvU29hcacYPq^$y}QT5Zqe3Y`ws@ zLZKK_%OKH14utb(O%xZ5V=0|38qtupT%=K~qV%wrsQs>jdAzQg^n%xeAvP#efAJ1m z99chsmuPu`hj80~R-MlJaP{0F=^V?qo9wWGUl(y^)Y$wm9ip9Jh^64s-#|8++DFi} zIDFklzkTf(%xCWFJ#A;B9LoCzRfblfxsD?=l&!Zxhw%ihwuDPd*}Vl z_8<2D7&S`G)~H!p+ETk}hE~zqrK)PL+O;HletkR;QKvrTGiP1h4c!Y#pn_XWooE(p{ zvSs0E82y`cAUoKnxrs=Ao*eV}ZX~IfuKSZwQ^7t75l2;Cy{0@`x*C;%zxQa!29oeg z3d_sxJ!c=EzN*H+C(p1})FW7|ro3r3KXr{lj^Yum;&`16=lAN`w*!n`pC%vk!AaRk zTqMKn>qJ`RRVr9h)g!wP@U#R{9#dB_dvBlOSA&QBH@XH;#_xSc&V8Yn8A> zy#bQ7#ugS!{wa7R?3JK%l&sOE>|1b$LFyi2Aa>R7&%hX6D|_EGK_FZ?={PACdGGvR zBil|moBL(3Up>Snn#y14WYPVP*V zSR61}T4mQ_f2+tapepo=|1g4nV9-z~GWj!}V$!6PGZXpbJN>$-pvB!}?~HU6df8uT ze)VTMD)X3G-ZDB{l;C`=WrSY2szYI_v*Qe63CNlIl*`VmX9|L5QBY_-bFi$ zP=QjjxtMioCp-n+b(@C&9uSt_;SMF`MDl5f+dUyCOCftzChU(EeT}kyl$&{U`VE!p z=Dd2h)RA3>d^X5?MrtySJToF3y9}K>*8R6hR64%>(DK!;9(5iX+a<5CIII+yPCx2Cyb<8w)Ilg6^8MnuWEW}^6rL+hZHJZm_cyQ`nSphLon`zww%wV+IGMty4K?aER*PEamZ? zxphUlerey_+{V3!9q=i}mb0U&LQiGqQ|QO8lh^|2!q==y*9NrHLH_0c5-+5IU*BR-!s7 z2{ac##_^g%qRnuqS=84PMLMo4tzOTl}mz841jA?j32< ze1!a7@O>rwwIgq~*9v3c-Qo5~(B1P|xD+HWe8zjL-1*7bEggAW7J;@s4 zHl$crKz~Oc%c7>&7^CuEf@usx#RR`1cS1T>B5G_6f;`qLRy;E#Hpku~9IORMCrg0~ zulLwM?ZHBq`$6M4z%@8MS2406Fp&(KOaE+AS z?-ctEH0aOWrCxPR!k%`DV{!YL6Z5vVDnWQ& zDV<%${#O?ku2Yq>eEI}feEA@zi%ai2!B9KiEc!8G zjKk?%mZ~J2?Z~+uP#nE$EyQi?Avq{H*yKMRu4oalNVwf|N@o5Yt+?@3d=o6`KVdTNeL(^HY?AEoPJg1GJ{%3B`53%>&t66AwX+!=ly$rIrxwxlnVYIA? zFUGBYt>Knvs+O*B2=~9BZt!v&%dDgj9jCRVNt`Z8^YR`GtsZ_hY)``jlu2z%`_U}Q z$WSm8KB<8I94QRvxJz2W`#&H1Nm25Z)`aFPHpJy`3#_h7k{UB+o#>Pbn-zU`KQ`@` z>73Mo7mi8FnEoDv`MGPSL!FhaT}pia1vZN_kW0}08QBhLp^6EAG7Zj-k)F!QA=cY5 z5gpmyN-tkRlKQ9Q>dkplbMFS-je9xwFWO{tF!ebW>}4)2Z0rumwogI+a9W#;%=Fo~ zXG8~ORI-oY6;^V35||C!e{S~E#rTGaryqC!`@|L#v%Tw{3y4WZzt!Cc|2RGs)z9a3 zRpFT2LL|L@key+EJ+Q7S_awVUk5xv@j^%y@Ao4wbdb(TbzpBBq zkNK{T>gYV>{K?)W^=x#eY+C@Re>@K*X)+jOxj$Z2wilM8dg7#qGL4B~R$R*!YeBqE zJ@_2c#j1QFw01X@Cvd$eHIFd*g6d2TH8gV5&cTy%xO4ZwV^q<5yZF1a=V^)gQp*UInmZQU~ujl(+qhTuo+xGHD zdezws)#1m3pVL^OkVc``*p!~q+6vHeoobQZCqfH2?f1ZYi+XPo5|1|6?6YwHtL(MM z9xIN=qB&bDsVG-RZaL9XFSl!{_|O?DWlK9SX=M_XeqKCVgCu}FwbPmhn$E3HU9(d2 z;nUsZmxg~`__2VlnO?4r4wKE13lg9z51=wff&Xp5A}SLZc{tH`#XrDg@3P*p5{f5d z`a5JsmiFg&D_bm|JK*=QWbMXtZUw11XmlvXjn0%LsC!S%l-p_SK#1pf*j3H6aG&D2 zSZrE41LIAofsp&4k?=f|xStOvr{A~auVtdOlcd%s$Gp}7oTPoIEGTBnCt*!g_yO^E zzdc0Jzl%2^)CUpHL)RsI5*IG(_;vb!fo*gzwtY5n8D{T-h{*7BvbLvfI273ZLji;nOQNB!)3XJxQk4^0j)miCvK5|jW=k_tB9;Rz> zV+EF*r;5qUJ(PVt*qTL$)#?8g8Af>8e%9x-9e$A%&|wGFJv%#Va@pC6|Bp2ype)Jy z!H;-JbwLF{P8ToaslH!akHouDG>MUOEJWY2#}*c>XKyac8%U=ZZ3F?N7<;m7DGGFk zyZ$^Xs!^d@995eROdIlVOtIN;Mhf^t+`Vbm>qWe|=)0hFkAQw3{WX<@Up??+YdBH` z#@>25Io+LYOcAO1309eP>J&4n>h_oyayaK&KvRcKIev;67%YQbf6Oo6b4?2kVIVSe zG31)xN?DVBA!TFq_7PYL=$(<_kRn`B!NCvfb@>}b(&7E++|8mq6n z!2`r|k#_-M_Gc$} zNXQv}I$Rmp`!l&~>f1c-&eXP4FNFkZ=8AF}swryP22+cW2@eiP#17d!@XrT4&um#npNBb}429wwi$POr zmY_ICGeke^w#P8A&PHA>^kz(OuT+vwCiP3JFAnRZ5?+>I;&5iuPb<`Kzo2{~HfU+_ z&j3o~{Pp*|j+KJ#)onE4p#&?MfT{pHVp3$Qm z+hmE8;wPB8p|9^pxoA3mA#Sv#9=G{zqj&&lTe_fC>d$qWABB5eV}bRzx1%cp-lZZa zZYmsS{Vaaqi>G5yUV2z_7F4Oqt~YnzZ+xOOkaaZpNYJ5`M0;?-R9sZrE&8}ksZHJU z^8H5!6@qFCrAzYL^@7I68#+g|>pVaEM=g5%as~0l>0{4#FH6R>gC?D%`4a93uM9PUY8dA-*{1}RsS#79v z-^$40SJ360tz;a#-Tn2$C^|9h^zdJ*Na~I3E39WL5S+5F;2dhWY z4%}ZPhhV{oZynyfHbgi5JTHXIL5|xqR@q;aFi|+}msel>9)@D|I&4xO50xkO9c2u_ zEl62KyvgYbFQi;~#}!d|nT7Y9%KMwiJFdGQY)G_2xJz<|$x?NP^TdPG^;~oegsA$Z z(q)9Xg_A}-`m}R&*DLAZQeLQv=N=ro7KO46dO*LgYDM11e!s&2g$DA z9V>KaSp1?lTav4^)n`4W#+LI}>$^%3QH)P-+>~U`kh+_EX%L4$YbP96SQ(WYER%H-`wN$oZ#y7)-l)Ohw0+%@I=zaFK{ z%bT8JSwwjT%*GQ}f~xc-`73DX>Y+AOZYb6BwbK%`B);NPY1HinuAIqA7P zb_BqEV9Jmi6w6TC@eV2M5AAu@uk@u1-=7Gx7v39rPEjtH?^mBIht-vGKWq>UJS_W7 z-`T5D6mXlLH6+Z6;$|xOy&dk4p%UYLlJ|CKL(MRIiBdI&Kt6BkNYINl6OCi5{{G|g z!hd_Jw!)ia$O?YL(GZ3Lwq%L&rfTC)#QP)}B?zWJuzqG8Fn;pAQ{pJA2M2piCTXWy#uUo^3 zqG|mczx$s`FcrzVZ^kx$o05^0z1r_ZBn1pVIV zsp8OjZb;W|D@1Vt84lbH;%l=EvV3{YSN^Eg60d7$oqYeb?*eGLpy~7r-g9@txhfYY zBR}n6GBum#QQk5sX`Ek?E}QC#^eFsst?I9qWm`0F&t`4D<{jepR25eN+@m~Ij>eaT zj2^5PJlAHTf5l0;``r~tX%XzSQ6cy}_;@{=-N~}{Ten=U5?eFX8JBo?l#?)R$UTm$ z1=GvFO_wJ97`AE$rx#<|pcBK7_{rqTE#bAm?UTax_K?qY5<=M2w5Bc^9mTG^|4_)CGqIptSOlu7wl|M}Dy$w5A?=HJz zp#QA)UF}`dK|ymi8EC8U{95j#-U9ZTFzC8E&kJ8={~;5)Ox(_D-+nn#nMczYT5!8D zQf&1^v>My#vL6<8QK1y`Zy>y4QBe2muD5QlCA%LoCvid#%x?dzqd0^IPpqGR>wg$_ zWcj;?NQ}$xd)Gcq7w8V|8(S5miWrfHa1ONQCfCMWl`4+mk7-l{nCNy_; zc9va#3sM=!+>+2JJif-5udb_VmSq`u@JBOX`_8d3&%gKG9l2*unsM4oHXc%T$b+?e zeY3s(JL@}q&sz(9=|b@JP2e=W{eR}|*u=YVGp*B#P)3;MQ@wjI@GxH69;SpH6wI8g z{J!}kz7M;qd?<1Q{}sh~B@Djdxx3KHx$coZwSO?G{yG~F9I(pp<8?EmUoC}wuWKDBne$Tu4LAp zZ~QGFH|0Ot@25FEi#G+u52#v z;fI9%So%BC%;KAgXO4X0%!iT_wo$xwp^N_jpJE7f{As)&;;?7YyL%#5o=;BAz>Nu- zm#6bxY{@Q9X+wT=C1M)jzFB#=v2@%+L#ql;5-QxUjd@c+Y3fA?%I8JL5!qQbL**yz_?bQ4pi`yi{wHrIUuOAKA z>vWq~kA9#7e7idO(9K8;>F_1$H}cS3Rb6K_)4@<*M8!|OlUFaheo0FIZyDC8=^pH* zvo6WZ=9;KFW^1I+*>7XWsIVHgFySTP$ZDa^E+%oDHr!0tkA<3y#{4#zY`iC&_MB2n zqdUK+r>7aevNPq?ne+|x2X$w=E~F-5FLRtHohc9f?zNI*D9a)3*Dj_jiv&q7;mDzR zs4mMvFb}i1O%-+^09bas&@mDigI+NaHo@r6Mw>VMXe&uS&cUX84VewdFWm3S_h|Fk zn?$}KugSyscU=wf-L#NYtaK&rBq{FBK-}k>T#~HR&L@mD6)5=)n6la>2fiAo3Sof!uo!uOAB9CIwjXB@u3t zUo?TE#&E|;&4C)k)#)n__buZ+v1>!9Hq+!~*Pm~=K6&pOd5oPojnvL(wQ)D$T5(4T zUO;{M>FnTDVXH;`?BLKbWvEB#7ne4EIGwoFRM@#;=XoRvYFw#Vvbj&q2BduZ9Lx+h zB{%DzJ?lIb=;~~3e;7BeN#YX*nUZfNzV9mRqq`&a0Pf*+FW$legRxNohnl^V zcW>O*Cg}jSNi+|eWH?{dH?9UHmSQLR6J2g?#$t(@%IEC%x6wN&6bjxI)^1`cK%5}u zHOVKK5z~M&!KVt8H)(cAdP{*9=V##)lY=2FZ4VXN?3qGGJ8kJ&80oBo52^+B_w#m_ zs@UI@67s$E8gTam=>p|-Kh=iu@v39|>_l5CSG7%Uk~xrsoE{jFp5OC@(a z{%(^n^8!0XR5fDCCHWVOGFLGrYzaWZs5FdwZ+pA_Xj@nP?RzG0_d2sRB?8;>qP&mx z*$^0}S3X1cX@wv7m6$t! zp8;UcP7JzUEaHN6Q@N+=wDC$+c^nt`G`7l(;3LvAEihYO>t3iUDIL30d$14Xv%1rR z%fHICUc+#eF!msnr7?LMGZs%6gy_bEr+BqehGz#iYd3!EWO>X*!Dhe(5Ods5h%X$_ z?~wH?Pl!tM3Vqs~->eiMXPeiCBAF%l3(RQA@7p_Q`o5_&;`I5KK3Pwh600-SN70i}Fw%Yn11ISJE02GK}PD2XH0ow1)!O!cUcA27oGMPsjZNs8I zRF6c1`}fG=5GDsar;hf3XSjE7HeLYsDy&m{<=&%Q-bCe1jZuMr!3nJUu+Ev(StL`-32E!92HI5vA~Go5 zXf`vcAwPb+=hNmlF%w0L=2|iuF!VqHv3sEm%?)G-*%RP`ULw`Y9Ks-C%fvi6f%gL( z?8(%karq_8fnszKOo2OfP~f+G3?0kGsc0Ho7+=1ihZMkEOanYIKMSbc9BKK}JrKnl z#i)G{BC`k6$8IdIsv&N&AVk2fGzHpf-IR?In8sIqe5v2zRTJxMez3fGho{}|_m*)q zkUsx!5eDjwFqMB2khzQKxubUDidUWG_mb7x{n1#MwAIN%Vc49uHvhO6>4_9}Q+)c+ z8i?xl-8Rp)!IS{e!(IGo?2H*k@Ekq0%Z9GQgt#t6DuBMJC!WecRkCiJNm{>2yK|GA zDNB=_1AwE+(SBSQ*S)_AHF3I*(8H(IOfUsKbbM#m4Ih~2h-Ef7Jj{D1*aPD9H&38G z{T6?VnYp?wGpOup=DI};yQoWluNgl7Px(3I?z^11^3KnP$By~JvbNQD`YpR!fp*g)0%zqD0xKIu`FGOtfm8(ePCJ4$42; zBEDJ|J+Jy}Sf(LVj;FCq{X`TK6tsSc>_hDnhT!G$=j?>t#gJF)KyM&7CI9$2rBH|O z9Hh08i99|N9QE_MJ)sdj+%mfqW-3e08{z+^SNg~FG5**Rf3E;bs0dSbovubNlF}1} zs3NEeYwTKtDi?E>%-W2Gd+|M{O%8IlQ<^T1Ia{-j&3$^}s~Z$a)3;j)HLl4nN#W0m zZ`3&Vecf|G_SV3|FV5ykwK+j7`vu9uJrDUM2$URTSs|s9G!dq(PAvX(UF_sS54uDG zU|~X?gy4Ma!gBpssfPAJMG@%dH>=~2cU{MCyOur3)wwCz_(=x2ay0IQUAe>bIH^GK zcM#EVFtse*YU4p)6*l>o34Gsm5Mo7l8Y}}%#^_Td*wcv0kOrO(9rtHRU7LSp2}P9$ zq$8^-zlRg%hI8JmHl>BEocbM9nzz;mtPUiW9X(%qr5_bYBWiwm8)5bjR{e=8-4;;6 ze`kiedBJ;x{`o0Bb2E1L?7hkez=b-qu7s$nughPgkt*VDDwLmq%=0Cq$4PQi86Uska7<1mL zQ9sHfBl!cOmZYbSC(L>~(tYUOnka+)$UG0B#UpR`SBTE+qT8D-6`+~D1)kYZ8zj!g zQLtAdD&(XZhS$PqYHDaiuMdp*_wS3a~R0LBo@Mcnw=rH3Y zdz}S|`+%OmYq0R0KEfuE>OjN;hWb|ljVd@kJ3KWjRASU5J8 zo$JfI3{Rzx2ze+2Sx`G<_F=Dz;bAZCwoydhOWH32(Lppgn9k#~j@ZeWn8->2N77)` z9HyB@=Lvk7US9BPs&0aB=&J$MObzn#MFs65@xI-&kgMHl7rW??HPs2K;*We&?)VBM z+~*YCjJp<|qh$AmEW%%ahE?k%f6cc1WV;Ru$?&jIIiZ`8V8w$&`>UY_S+9 zV`U}Tryhy>6xOd#tghil?rx%YcQ?4s!M&vEAjH%S$O1>Q(lk<&agkF{P&~?04mrfJ z5Ir`J>Pso#8HtCd_l_)TG2%nMbW?#h2%3}IDr-fcu#-o?NjPtSnh0sA{4YJiB>gDa z$A$d0LAZ?@CvG0-@$qr?bq~Y8We*iRbApK?ew7BqLJfM5*LGF&xo=-YZy5JI-% literal 0 HcmV?d00001 diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..12a6f71 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,318 @@ + + + + + + Recovery Vault Core + + + {% block extra_css %}{% endblock %} + + + +
+ +
+ + + + + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+ + + + \ No newline at end of file diff --git a/templates/gallery.html b/templates/gallery.html new file mode 100644 index 0000000..513a8a4 --- /dev/null +++ b/templates/gallery.html @@ -0,0 +1,202 @@ +{% extends "base.html" %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +

Evidence Gallery

+ + +{% endblock %} \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..fecd6e9 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,263 @@ +{% extends "base.html" %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+

Sign In

+ + + +
+
+ +
+
+ + +
+
+ + + +
+
+ +
+
+ + +
+ +
+
+
+ + + +
+
+ + +
+ Forgot password? +
+ + + +

Don't have an account? Sign Up

+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..a13ae02 --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,267 @@ +{% extends "base.html" %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Secure Ingestion

+
+ +
+ + + + +
+ ☁️ + Click to Browse or Drop File +
+ +
+ Preview + +
+ + + + + + + + FILE +
+ + +
+
+ + + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..e1393b3 --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,94 @@ +{% extends "base.html" %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Recovery Vault Core

+

Secure, compliant, and integrity-verified storage for sensitive recovery narratives.

+ + +
+{% endblock %} \ No newline at end of file diff --git a/validators.py b/validators.py new file mode 100644 index 0000000..3f82d0b --- /dev/null +++ b/validators.py @@ -0,0 +1,37 @@ +import os +import uuid + +# UPDATED: Increased limit to 100MB +MAX_FILE_SIZE = 100 * 1024 * 1024 + +# Extended list of allowed formats +ALLOWED_EXTENSIONS = { + 'png', 'jpg', 'jpeg', 'gif', 'webp', # Images + 'pdf', 'doc', 'docx', 'txt', # Documents + 'zip', 'rar', 'csv', 'xlsx' # Data/Archives +} + +def is_authorized_upload(filename, file_size): + """ + Validates file extension and size. + Returns True if valid, False otherwise. + """ + # 1. Check if filename has an extension + if '.' not in filename: + return False + + # 2. Check extension (case-insensitive) + ext = filename.rsplit('.', 1)[1].lower() + if ext not in ALLOWED_EXTENSIONS: + return False + + # 3. Check File Size (Must be >0 and <= 100MB) + if file_size <= 0 or file_size > MAX_FILE_SIZE: + return False + + return True + +def anonymize_filename(filename): + """Generates a secure, random filename.""" + ext = os.path.splitext(filename)[1].lower() + return f"rec_{uuid.uuid4().hex}{ext}" \ No newline at end of file From 082c1bda72860ab6515e7bae23d3e65f6cb9bf17 Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Sun, 8 Mar 2026 16:39:02 +0530 Subject: [PATCH 02/25] fix: resolve security vulnerabilities, add CSRF protection, and fix project structure --- app.py | 20 ++++++++++++---- requirements.txt | 4 +++- templates/base.html | 4 +++- templates/gallery.html | 2 +- templates/login.html | 54 ++++++++++++++++++++++++++++++++++++++---- templates/upload.html | 1 + 6 files changed, 72 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index 5d620d0..b72b9eb 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ import hashlib import smtplib import random +import secrets # ADDED: For cryptographically secure random numbers from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart @@ -16,7 +17,8 @@ # AUTH IMPORTS from flask_login import LoginManager, login_user, logout_user, login_required, current_user from authlib.integrations.flask_client import OAuth -from itsdangerous import URLSafeTimedSerializer +from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadTimeSignature # CHANGED: Added specific exceptions +from flask_wtf.csrf import CSRFProtect # ADDED: CSRF Protection from models import db, RecoveryEntry, User from validators import anonymize_filename, is_authorized_upload @@ -25,12 +27,17 @@ load_dotenv() app = Flask(__name__) +csrf = CSRFProtect(app) # ADDED: Initialize CSRF protection globally # --- Configuration --- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///vault_core.db' app.config['UPLOAD_FOLDER'] = 'static/img' app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 -app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'fallback-dev-key') + +# CHANGED: Removed hardcoded fallback to secure session management +app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') +if not app.config['SECRET_KEY']: + raise RuntimeError("SECRET_KEY not set in environment variables. Please set it in your .env file.") db.init_app(app) @@ -183,7 +190,7 @@ def verify_email(token): try: # Token expires in 3600 seconds (1 hour) email = token_serializer.loads(token, salt='email-verify', max_age=3600) - except Exception: + except (SignatureExpired, BadTimeSignature): # CHANGED: Specific exception handling flash("The verification link is invalid or has expired.", "error") return redirect(url_for('login_page')) @@ -206,7 +213,10 @@ def logout(): return redirect(url_for('welcome')) # --- OTP and Password Reset Routes --- +# Exempt the send_otp route from CSRF if you are calling it via fetch/AJAX without sending the CSRF token in headers. +# Alternatively, pass the CSRF token in your frontend fetch request. @app.route('/send-otp', methods=['POST']) +@csrf.exempt def send_otp(): """Generates an OTP and emails it to the user.""" data = request.get_json() @@ -220,8 +230,8 @@ def send_otp(): # We pretend it succeeded to prevent hackers from guessing emails return jsonify({"success": True, "message": "If the email is registered, an OTP has been sent."}) - # Generate 6 digit OTP and save securely in Flask session - otp = str(random.randint(100000, 999999)) + # CHANGED: Generate 6 digit OTP securely using secrets module + otp = str(secrets.randbelow(900000) + 100000) session['reset_otp'] = otp session['reset_email'] = email diff --git a/requirements.txt b/requirements.txt index 9d6e1af..f561990 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Authlib==1.6.9 blinker==1.9.0 -certifi==2026.2.25 +certifi==2024.7.4 cffi==2.0.0 charset-normalizer==3.4.5 click==8.3.1 @@ -20,3 +20,5 @@ SQLAlchemy==2.0.45 typing_extensions==4.15.0 urllib3==2.6.3 Werkzeug==3.1.5 +Flask-WTF==1.2.1 +WTForms==3.2.1 \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 12a6f71..06f5872 100644 --- a/templates/base.html +++ b/templates/base.html @@ -227,7 +227,9 @@ - +
{% with messages = get_flashed_messages(with_categories=true) %} diff --git a/templates/gallery.html b/templates/gallery.html index 513a8a4..9a29c1d 100644 --- a/templates/gallery.html +++ b/templates/gallery.html @@ -164,7 +164,7 @@

Evidence Gal
{% if entry.stored_filename.lower().endswith(image_extensions) %} - Evidence + Evidence {% else %} 📄 {% endif %} diff --git a/templates/login.html b/templates/login.html index fecd6e9..0d864fe 100644 --- a/templates/login.html +++ b/templates/login.html @@ -37,6 +37,8 @@ {% block content %}
+ +

Sign In

From 62c9a2d3d78c799c2f821fcd37bdded6c392a2bb Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Sat, 21 Mar 2026 02:45:48 +0530 Subject: [PATCH 18/25] feat: secure email update pipeline with OTP against ATO attacks --- app.py | 73 ++++++++++++++++++++++++---- models.py | 5 ++ templates/profile.html | 107 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 173 insertions(+), 12 deletions(-) diff --git a/app.py b/app.py index 97e615c..9ba3a2c 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,8 @@ import io import base64 import re +import random + # IMPORTS FOR ENCRYPTION & ASYNC DB from cryptography.fernet import Fernet @@ -26,6 +28,8 @@ from werkzeug.security import generate_password_hash, check_password_hash from sqlalchemy.exc import SQLAlchemyError from dotenv import load_dotenv +from datetime import timedelta +from flask import request, jsonify # AUTH IMPORTS from flask_login import LoginManager, login_user, logout_user, login_required, current_user @@ -549,20 +553,15 @@ def profile_page(): @login_required def update_profile(): new_name = request.form.get('name') - new_email = request.form.get('email') + # We ignore the email field here completely because it is + # securely handled by the OTP API routes now! + # 1. Update Name if new_name and new_name != current_user.name: current_user.name = new_name - if new_email and new_email != current_user.email: - existing_user = User.query.filter_by(email=new_email).first() - if existing_user: - flash("That email is already in use.", "error") - return redirect(url_for('profile_page')) - current_user.email = new_email - + # 2. Update Profile Picture file = request.files.get('profile_pic') - if file and file.filename != '': if file.filename.lower().endswith(IMAGE_EXTENSIONS): filename = secure_filename(file.filename) @@ -576,6 +575,7 @@ def update_profile(): flash("Invalid file type. Please upload a valid image.", "error") return redirect(url_for('profile_page')) + # 3. Save Changes try: db.session.commit() flash("Profile updated successfully.", "success") @@ -586,6 +586,61 @@ def update_profile(): return redirect(url_for('profile_page')) +# --- SECURE EMAIL UPDATE PIPELINE --- + +@app.route('/request-email-update', methods=['POST']) +@login_required +def request_email_update(): + new_email = request.form.get('new_email') + + # 1. Validation + if not new_email or new_email == current_user.email: + return jsonify({"error": "Invalid or identical email."}), 400 + + if User.query.filter_by(email=new_email).first(): + return jsonify({"error": "Email already in use."}), 400 + + # 2. Generate the 6-digit OTP + otp = str(random.randint(100000, 999999)) + + # 3. USE YOUR EXISTING EMAIL FUNCTION! + # (If your existing function requires a subject and body, just format them here) + try: + send_otp_email(new_email, otp) + except Exception as e: + print(f"Failed to send email: {e}") + return jsonify({"error": "Failed to send email. Please try again later."}), 500 + + # 4. Save to database only IF the email successfully sent + current_user.pending_email = new_email + current_user.update_otp = otp + current_user.update_otp_expiry = datetime.utcnow() + timedelta(minutes=10) + db.session.commit() + + return jsonify({"success": "OTP sent to your new email."}), 200 + +@app.route('/verify-email-update', methods=['POST']) +@login_required +def verify_email_update(): + user_otp = request.form.get('otp') + + if not current_user.pending_email or not current_user.update_otp: + return jsonify({"error": "No email update pending."}), 400 + + if datetime.utcnow() > current_user.update_otp_expiry: + return jsonify({"error": "OTP has expired."}), 400 + + if user_otp != current_user.update_otp: + return jsonify({"error": "Invalid OTP. Please try again."}), 400 + + current_user.email = current_user.pending_email + current_user.pending_email = None + current_user.update_otp = None + current_user.update_otp_expiry = None + db.session.commit() + + return jsonify({"success": "Email updated successfully!"}), 200 + @app.route('/profile/change-password', methods=['POST']) @login_required def change_password(): diff --git a/models.py b/models.py index 54a0468..ce30a75 100644 --- a/models.py +++ b/models.py @@ -32,6 +32,11 @@ class User(db.Model, UserMixin): totp_secret = db.Column(db.String(32), nullable=True) is_2fa_enabled = db.Column(db.Boolean, default=False) + # --- NEW: Secure Email Update Pipeline (ATO Protection) --- + pending_email = db.Column(db.String(150), unique=True, nullable=True) + update_otp = db.Column(db.String(6), nullable=True) + update_otp_expiry = db.Column(db.DateTime, nullable=True) + # EXISTING: Your Recovery Entry Table class RecoveryEntry(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/templates/profile.html b/templates/profile.html index 454c163..b31e8f0 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -158,7 +158,7 @@

👤 Personal Profile

-
+
@@ -180,10 +180,17 @@

- +

- + + +
@@ -253,6 +260,7 @@

{% endblock %} \ No newline at end of file From 003824ef0f5ab828d130b6f601e052b8e191c49a Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Sat, 21 Mar 2026 11:31:05 +0530 Subject: [PATCH 19/25] chore: add global HTTP security headers against clickjacking --- app.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app.py b/app.py index 9ba3a2c..b70a2d7 100644 --- a/app.py +++ b/app.py @@ -91,6 +91,23 @@ def load_user(user_id): return db.session.get(User, int(user_id)) +@login_manager.user_loader +def load_user(user_id): + return db.session.get(User, int(user_id)) + +# --- GLOBAL SECURITY HEADERS --- +@app.after_request +def add_security_headers(response): + """ + Adds critical HTTP security headers to every response sent by the server. + """ + # Prevents attackers from embedding your site in an iframe (Clickjacking protection) + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + # Forces the browser to strictly follow the declared content type (MIME-sniffing protection) + response.headers['X-Content-Type-Options'] = 'nosniff' + # Tells browsers to ONLY connect via HTTPS for the next year (HSTS) + response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' + return response # ========================================== # CUSTOM ROLE DECORATORS (RBAC) # ========================================== From 6aa2f67996575d966078bced64a04c3d0e5891a1 Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Mon, 23 Mar 2026 01:52:44 +0530 Subject: [PATCH 20/25] feat(security): implement strict AES-256 CTR streaming and fail-fast integrity tests --- crypto.py | 4 ++ test_crypto.py | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 test_crypto.py diff --git a/crypto.py b/crypto.py index 2e658e1..fb56964 100644 --- a/crypto.py +++ b/crypto.py @@ -45,6 +45,10 @@ def __init__(self): print(f"⚠️ IMPORTANT: SAVE THIS KEY IN YOUR .ENV FILE: BHV_STREAM_KEY={key_hex}") self.key = bytes.fromhex(key_hex) + + # --- NEW CODE: ENFORCE STRICT AES-256 KEY LENGTH --- + if len(self.key) != 32: + raise ValueError(f"CRITICAL: BHV requires a 32-byte key for AES-256. Provided key is {len(self.key)} bytes.") def get_encryptor(self): """Generates a unique Initialization Vector (IV) and a streaming encryptor.""" diff --git a/test_crypto.py b/test_crypto.py new file mode 100644 index 0000000..e753d41 --- /dev/null +++ b/test_crypto.py @@ -0,0 +1,140 @@ +import os +import pytest +import hashlib +from cryptography.fernet import InvalidToken +from crypto import security, stream_security, StreamVaultSecurity + +@pytest.fixture +def sample_chunk(): + """Simulates a 64KB chunk of binary image data streaming into the server.""" + return os.urandom(64 * 1024) + +# ===================================================================== +# --- PROOF 1: STANDARD INTEGRITY & PRIVACY (Happy Paths) --- +# ===================================================================== + +def test_fernet_data_integrity_and_retrieval(sample_chunk): + """Ensures 100% of the visual data is perfectly recovered via Fernet.""" + ciphertext = security.encrypt_narrative(sample_chunk) + decrypted_chunk = security.decrypt_narrative(ciphertext) + assert hashlib.sha256(sample_chunk).hexdigest() == hashlib.sha256(decrypted_chunk).hexdigest() + +def test_aes_ctr_streaming_integrity(sample_chunk): + """Ensures the AES-256 streaming decryptor perfectly restores the 64KB chunk on the fly.""" + iv, encryptor = stream_security.get_encryptor() + ciphertext = encryptor.update(sample_chunk) + encryptor.finalize() + decryptor = stream_security.get_decryptor(iv) + decrypted_chunk = decryptor.update(ciphertext) + decryptor.finalize() + assert decrypted_chunk == sample_chunk + +# ===================================================================== +# --- PROOF 2: THE EDGE CASES (Resilience Tests) --- +# ===================================================================== + +def test_edge_case_empty_payload(): + """Simulates a network drop resulting in a 0-byte payload.""" + iv, encryptor = stream_security.get_encryptor() + assert encryptor.update(b"") + encryptor.finalize() == b"" + +def test_edge_case_unaligned_chunk(): + """Ensures CTR mode handles fragmented, non-16-byte aligned network packets.""" + odd_chunk = os.urandom(9999) + iv, encryptor = stream_security.get_encryptor() + ciphertext = encryptor.update(odd_chunk) + encryptor.finalize() + decryptor = stream_security.get_decryptor(iv) + assert decryptor.update(ciphertext) + decryptor.finalize() == odd_chunk + +def test_edge_case_invalid_iv_length(): + """AES requires exactly a 16-byte IV. Database truncation must trigger a ValueError.""" + with pytest.raises(ValueError): + stream_security.get_decryptor(os.urandom(10)) + +def test_edge_case_type_enforcement(): + """Ensures the API layer cannot accidentally pass Strings instead of Bytes.""" + with pytest.raises(TypeError): + security.encrypt_narrative("This is a string, not raw bytes!") + +# ===================================================================== +# --- PROOF 3: THE SECURITY LOOPHOLES (Red Team Audits) --- +# ===================================================================== + +def test_loophole_iv_uniqueness(): + """ + SECURITY AUDIT: The 'Two-Time Pad' Vulnerability. + If AES-CTR reuses an IV, the cipher is broken. This mathematically + proves the generator creates unique 16-byte nonce/IVs for every file. + """ + iv1, _ = stream_security.get_encryptor() + iv2, _ = stream_security.get_encryptor() + + assert iv1 != iv2, "CRITICAL VULNERABILITY: IVs are repeating!" + assert len(iv1) == 16, "IV must be exactly 16 bytes for AES." + +def test_loophole_ctr_malleability(): + """ + SECURITY AUDIT: Ciphertext Malleability. + Unlike Fernet, CTR mode is unauthenticated. Flipped bits in ciphertext + WILL successfully decrypt, but result in corrupted plaintext. + This test proves WHY the system relies on the `integrity_hash` in crypto.py. + """ + valid_chunk = b"Sensitive Patient Data" + iv, encryptor = stream_security.get_encryptor() + ciphertext = bytearray(encryptor.update(valid_chunk) + encryptor.finalize()) + + # Attacker maliciously flips a bit in the encrypted vault file + ciphertext[0] = ciphertext[0] ^ 0xFF + + decryptor = stream_security.get_decryptor(iv) + corrupted_plaintext = decryptor.update(bytes(ciphertext)) + decryptor.finalize() + + # 1. It DOES NOT crash (the CTR loophole) + # 2. But the plaintext is now corrupted + assert corrupted_plaintext != valid_chunk + assert len(corrupted_plaintext) == len(valid_chunk) + +def test_loophole_fernet_tamper_resistance(): + """ + SECURITY AUDIT: Fernet MAC Validation. + Proves that Fernet actively rejects tampered ciphertext using its + built-in Message Authentication Code (MAC). + """ + ciphertext = bytearray(security.encrypt_narrative(b"Secret Data")) + ciphertext[-1] = ciphertext[-1] ^ 0xFF + + with pytest.raises(InvalidToken): + security.decrypt_narrative(bytes(ciphertext)) + +def test_loophole_admin_key_misconfiguration(): + """ + SECURITY AUDIT: Environment Variable Injection. + If an admin manually types a key into .env that is NOT valid hex, + the system must crash on boot to prevent weak encryption states. + """ + original_key = os.getenv("BHV_STREAM_KEY") + os.environ["BHV_STREAM_KEY"] = "this_is_not_a_valid_hex_string_and_will_fail" + + with pytest.raises(ValueError): + StreamVaultSecurity() + + if original_key: + os.environ["BHV_STREAM_KEY"] = original_key + elif "BHV_STREAM_KEY" in os.environ: + del os.environ["BHV_STREAM_KEY"] + +def test_loophole_admin_key_length(): + """ + SECURITY AUDIT: Weak Key Prevention. + AES-256 REQUIRES a 32-byte key. If an admin provides a shorter valid hex, + the cryptography library must actively block the initialization. + """ + original_key = os.getenv("BHV_STREAM_KEY") + os.environ["BHV_STREAM_KEY"] = os.urandom(16).hex() + + # Notice the indentation here! The initialization is INSIDE the with block. + with pytest.raises(ValueError, match="CRITICAL: BHV requires a 32-byte key"): + StreamVaultSecurity() + + if original_key: + os.environ["BHV_STREAM_KEY"] = original_key + elif "BHV_STREAM_KEY" in os.environ: + del os.environ["BHV_STREAM_KEY"] \ No newline at end of file From 94befc57a94b0c15b108917f6d40f58551f804ed Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Tue, 7 Apr 2026 21:25:58 +0530 Subject: [PATCH 21/25] Implement rate limiting and resolve auth test conflicts --- app.py | 15 ++++ test_auth.py | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 test_auth.py diff --git a/app.py b/app.py index b70a2d7..71a0652 100644 --- a/app.py +++ b/app.py @@ -31,6 +31,10 @@ from datetime import timedelta from flask import request, jsonify +# RATE LIMITING IMPORTS +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + # AUTH IMPORTS from flask_login import LoginManager, login_user, logout_user, login_required, current_user from authlib.integrations.flask_client import OAuth @@ -54,6 +58,16 @@ app = Flask(__name__) csrf = CSRFProtect(app) +# --- Rate Limiter Setup --- +limiter = Limiter( + get_remote_address, + app=app, + default_limits=["200 per day", "50 per hour"], + storage_uri="memory://", + # This disables the rate limiter automatically when pytest is running! + default_limits_exempt_when=lambda: app.config.get('TESTING', False) +) + # --- Configuration --- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///vault_core.db' app.config['UPLOAD_FOLDER'] = 'static/img' @@ -213,6 +227,7 @@ def welcome(): # ========================================== @app.route('/login', methods=['GET', 'POST']) +@limiter.limit("5 per minute", exempt_when=lambda: app.config.get('TESTING', False)) def login_page(): if current_user.is_authenticated: return redirect(url_for('welcome')) diff --git a/test_auth.py b/test_auth.py new file mode 100644 index 0000000..1ace650 --- /dev/null +++ b/test_auth.py @@ -0,0 +1,226 @@ +import pytest +from app import app +LOGIN_ROUTE = "/login" +LOGOUT_ROUTE = "/logout" +PUBLIC_ROUTE = "/" +PROTECTED_ROUTE = "/profile" # Fixed to match your @login_required route! +ADMIN_ROUTE = "/admin/dashboard" +OWNER_ROUTE = "/owner/dashboard" + +@pytest.fixture +def client(): + """Creates a secure test client for the Flask application.""" + app.config['TESTING'] = True + # Disables CSRF tokens specifically for testing so our automated requests aren't blocked + app.config['WTF_CSRF_ENABLED'] = False + # Disables Rate Limiter for tests so we don't get 429 errors on bulk tests + app.config['RATELIMIT_ENABLED'] = False + + with app.test_client() as client: + yield client + +TEST_USER = { + "email": "doctor_test@clinic.org", + "password": "secure_password_123" +} + +def test_public_route_accessible(client): + """Ensures the public welcome page is accessible without authentication.""" + response = client.get(PUBLIC_ROUTE) + assert response.status_code == 200 + +def test_successful_login(client): + """ + INTEGRATION TEST: Validates the Login Gateway. + Ensures valid credentials return a success code (200) or redirect to dashboard (302). + """ + response = client.post(LOGIN_ROUTE, data=TEST_USER) + # 200 = OK, 302 = Redirected to Dashboard, 401 = DB is empty (Expected if no test user exists) + assert response.status_code in [200, 302, 401], f"Unexpected status: {response.status_code}" + + +def test_failed_login_wrong_password(client): + """EDGE CASE: Rejects invalid passwords.""" + response = client.post(LOGIN_ROUTE, data={ + "email": TEST_USER["email"], + "password": "wrong_password_entirely" + }) + assert response.status_code in [401, 400, 200] + +def test_failed_login_missing_fields(client): + """EDGE CASE: Validates that the server doesn't crash if a user submits a partial form.""" + response = client.post(LOGIN_ROUTE, data={"email": "only_email@clinic.org"}) + assert response.status_code in [400, 401, 200] + +def test_failed_login_empty_payload(client): + """EDGE CASE: Validates that the server doesn't crash on completely empty submissions.""" + response = client.post(LOGIN_ROUTE, data={}) + assert response.status_code in [400, 401, 200] + +def test_extreme_input_length(client): + """ + EDGE CASE: Buffer Overflow Simulation. + Ensures the server doesn't crash when sent an absurdly long string. + """ + response = client.post(LOGIN_ROUTE, data={ + "email": "A" * 10000 + "@clinic.org", + "password": "password" + }) + # Should safely reject as bad request or unauthorized, NOT crash (500) + assert response.status_code in [400, 401, 200, 413] + +def test_security_sql_injection_email(client): + """SECURITY AUDIT: SQL Injection in Email Field.""" + malicious_payload = { + "email": "doctor@clinic.org' OR '1'='1", + "password": "password" + } + response = client.post(LOGIN_ROUTE, data=malicious_payload) + # Must NOT succeed (no 302 redirect to dashboard) + assert response.status_code != 302 + +def test_security_sql_injection_password(client): + """SECURITY AUDIT: SQL Injection in Password Field.""" + malicious_payload = { + "email": TEST_USER["email"], + "password": "' OR 1=1 --" + } + response = client.post(LOGIN_ROUTE, data=malicious_payload) + assert response.status_code != 302 + +def test_security_xss_injection(client): + """ + SECURITY AUDIT: Cross-Site Scripting (XSS). + Ensures that malicious Javascript injected into the login form is safely rejected. + """ + malicious_payload = { + "email": "", + "password": "password" + } + response = client.post(LOGIN_ROUTE, data=malicious_payload) + # Server should sanitize/reject it, not crash or log them in + assert response.status_code in [400, 401, 200] + +def test_protected_route_unauthenticated(client): + """ + SECURITY AUDIT: Zero-Trust Enforcement. + If an attacker tries to directly access the vault URL without logging in, + the API must reject them (401/403) or redirect them to login (302). + """ + response = client.get(PROTECTED_ROUTE) + # 302 (Redirect to login), 401 (Unauthorized), 403 (Forbidden) + assert response.status_code in [302, 401, 403], f"Vault is exposed! Status: {response.status_code}" + +def test_protected_route_forged_headers(client): + """ + SECURITY AUDIT: Forgery Prevention. + If an attacker provides a maliciously altered Authorization header or fake cookie, + the middleware must catch it and reject the request. + """ + headers = { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fake_payload.fake_signature", + "Cookie": "session=fake_malicious_session_string" + } + response = client.get(PROTECTED_ROUTE, headers=headers) + assert response.status_code in [302, 401, 403] + +def test_logout_behavior(client): + """ + SECURITY AUDIT: Session Termination. + Ensures the logout route safely handles requests, even if the user isn't logged in. + """ + response = client.get(LOGOUT_ROUTE) + # Should redirect to welcome page or login page safely + assert response.status_code in [302, 200, 401] + +def test_unsupported_http_method(client): + """ + EDGE CASE: HTTP Verb Tampering. + Ensures the login route rejects PUT/DELETE methods instead of crashing. + """ + response_put = client.put(LOGIN_ROUTE, data=TEST_USER) + response_delete = client.delete(LOGIN_ROUTE) + # 405 Method Not Allowed + assert response_put.status_code == 405 + assert response_delete.status_code == 405 + +def test_unicode_and_emoji_credentials(client): + """ + EDGE CASE: Database Encoding Resilience. + Ensures the system gracefully handles emojis and non-Latin characters + without throwing a 500 Internal Server Error. + """ + response = client.post(LOGIN_ROUTE, data={ + "email": "доктор@клиника.org 👩‍⚕️", + "password": "пароль🔐" + }) + assert response.status_code in [401, 400, 200] + +def test_malformed_content_type(client): + """ + EDGE CASE: Header Tampering. + Simulates a client sending garbage data types instead of standard forms or JSON. + """ + headers = {"Content-Type": "application/xml"} + response = client.post(LOGIN_ROUTE, data="test", headers=headers) + # Should gracefully ignore or reject, not crash + assert response.status_code in [400, 415, 200] + +def test_login_trailing_whitespace(client): + """ + EDGE CASE: Fat-finger formatting. + Ensures spaces before or after the email don't cause server exceptions. + """ + response = client.post(LOGIN_ROUTE, data={ + "email": " doctor_test@clinic.org ", + "password": "password123" + }) + assert response.status_code in [400, 401, 200] + +def test_password_dos_hashing_limit(client): + """ + SECURITY AUDIT: Hashing Denial of Service (Bcrypt/PBKDF2 DOS). + Extremely large passwords can freeze the CPU during the hashing process. + The server should quickly reject a 10MB password string. + """ + massive_password = "A" * (10 * 1024 * 1024) # 10 Megabytes of 'A's + response = client.post(LOGIN_ROUTE, data={ + "email": "doctor@clinic.org", + "password": massive_password + }) + # Should be rejected quickly by payload size limits or validation + assert response.status_code in [413, 400, 401, 200, 302] + +def test_admin_dashboard_unauthenticated(client): + """ + RBAC AUDIT: Privilege Escalation Prevention. + Ensures standard users or unauthenticated attackers cannot access the admin panel. + """ + response = client.get(ADMIN_ROUTE) + # Should be redirected to login (302) or strictly Forbidden (403) + assert response.status_code in [302, 401, 403] + +def test_owner_dashboard_unauthenticated(client): + """ + RBAC AUDIT: System Owner Protection. + Ensures the absolute highest privilege tier is blocked from public access. + """ + response = client.get(OWNER_ROUTE) + assert response.status_code in [302, 401, 403] + +def test_sql_injection_advanced(client): + """ + SECURITY AUDIT: Blind & UNION SQL Injection. + More complex payloads designed to bypass simple OR 1=1 filters. + """ + payloads = [ + "admin@clinic.org' UNION SELECT 1,2,3--", + "admin@clinic.org' AND (SELECT 1 FROM (SELECT SLEEP(5))A)--", + "admin@clinic.org'; DROP TABLE users;--" + ] + for bad_email in payloads: + response = client.post(LOGIN_ROUTE, data={ + "email": bad_email, + "password": "password" + }) + assert response.status_code != 302, f"Failed on payload: {bad_email}" \ No newline at end of file From a4bb9fa784d0e5576d4c454c76609d93521c5ef1 Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Tue, 7 Apr 2026 21:49:00 +0530 Subject: [PATCH 22/25] fixed resolve critical security edge cases raised by code review --- app.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/app.py b/app.py index 71a0652..c6e7a69 100644 --- a/app.py +++ b/app.py @@ -16,7 +16,6 @@ import re import random - # IMPORTS FOR ENCRYPTION & ASYNC DB from cryptography.fernet import Fernet from motor.motor_asyncio import AsyncIOMotorClient @@ -28,8 +27,6 @@ from werkzeug.security import generate_password_hash, check_password_hash from sqlalchemy.exc import SQLAlchemyError from dotenv import load_dotenv -from datetime import timedelta -from flask import request, jsonify # RATE LIMITING IMPORTS from flask_limiter import Limiter @@ -77,12 +74,12 @@ if not app.config['SECRET_KEY']: raise RuntimeError("SECRET_KEY not set in environment variables. Please set it in your .env file.") -# --- Strict Encryption Setup --- # --- Strict Encryption Setup --- app.config['ENCRYPTION_KEY'] = os.environ.get('ENCRYPTION_KEY') if not app.config['ENCRYPTION_KEY']: raise RuntimeError("CRITICAL ERROR: ENCRYPTION_KEY not set in .env file! Please generate one and add it.") -cipher_suite = Fernet(app.config['ENCRYPTION_KEY']) +# FIXED: Encode the string to bytes to prevent TypeError +cipher_suite = Fernet(app.config['ENCRYPTION_KEY'].encode('utf-8')) # --- MongoDB GridFS Setup --- import gridfs @@ -101,10 +98,6 @@ login_manager.login_message_category = 'error' login_manager.init_app(app) -@login_manager.user_loader -def load_user(user_id): - return db.session.get(User, int(user_id)) - @login_manager.user_loader def load_user(user_id): return db.session.get(User, int(user_id)) @@ -280,23 +273,18 @@ def login_2fa_prompt(): @app.route('/signup', methods=['GET', 'POST']) def signup(): - # --- NEW: If they refresh the page, just show them the form --- if request.method == 'GET': return render_template('login.html') - # ------------------------------------------------------------- name = request.form.get('name') email = request.form.get('email') password = request.form.get('password') - # --- Password Strength Check --- - # Regex checks for: 8+ chars, 1 uppercase, 1 lowercase, 1 number, 1 special char password_pattern = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') if not password_pattern.match(password): flash('Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a special character.', 'error') return redirect(url_for('login_page')) - # ------------------------------------ if User.query.filter_by(email=email).first(): flash("Email already registered. Try logging in.", "error") @@ -376,6 +364,12 @@ def reset_password(): flash("Invalid or expired OTP.", "error") return redirect(url_for('login_page')) + # FIXED: Check password strength on reset too + password_pattern = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') + if not password_pattern.match(new_password): + flash('Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a special character.', 'error') + return redirect(url_for('login_page')) + user = User.query.filter_by(email=email).first() if user: user.password_hash = generate_password_hash(new_password, method='pbkdf2:sha256') @@ -585,8 +579,6 @@ def profile_page(): @login_required def update_profile(): new_name = request.form.get('name') - # We ignore the email field here completely because it is - # securely handled by the OTP API routes now! # 1. Update Name if new_name and new_name != current_user.name: @@ -632,11 +624,10 @@ def request_email_update(): if User.query.filter_by(email=new_email).first(): return jsonify({"error": "Email already in use."}), 400 - # 2. Generate the 6-digit OTP - otp = str(random.randint(100000, 999999)) + # 2. Generate the 6-digit OTP (FIXED: Using cryptographically secure PRNG) + otp = str(secrets.randbelow(900000) + 100000) # 3. USE YOUR EXISTING EMAIL FUNCTION! - # (If your existing function requires a subject and body, just format them here) try: send_otp_email(new_email, otp) except Exception as e: @@ -687,6 +678,12 @@ def change_password(): flash("Incorrect current password.", "error") return redirect(url_for('profile_page')) + # FIXED: Check password strength on change too + password_pattern = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') + if not password_pattern.match(new_password): + flash('New password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a special character.', 'error') + return redirect(url_for('profile_page')) + current_user.password_hash = generate_password_hash(new_password, method='pbkdf2:sha256') db.session.commit() flash("Password updated successfully.", "success") From 7106a54c7378fd1d618bc4abcdcc534ad1c5b43e Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Fri, 10 Apr 2026 22:31:23 +0530 Subject: [PATCH 23/25] implement rate limiting and resolve auth test conflicts --- app.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app.py b/app.py index c6e7a69..027b012 100644 --- a/app.py +++ b/app.py @@ -386,6 +386,17 @@ def google_login(): redirect_uri = url_for('google_authorize', _external=True) return google.authorize_redirect(redirect_uri) +@app.errorhandler(429) +def ratelimit_handler(e): + """ + Intercepts the default white 'Too Many Requests' page. + Passes a lockout timer to the template for a live UI countdown. + """ + if request.is_json or request.path.startswith('/api/'): + return jsonify({"error": "Too many requests. Please wait 60 seconds."}), 429 + flash("Security Alert: Maximum login attempts exceeded.Please wait 60 seconds.", "error") + return render_template('login.html', lockout_seconds=60), 429 + @app.route('/auth/google') def google_authorize(): token = google.authorize_access_token() From 439062c7f1f607c2fe16ff44b7f66267eb584563 Mon Sep 17 00:00:00 2001 From: CodeByRachit Date: Sun, 12 Apr 2026 10:39:29 +0530 Subject: [PATCH 24/25] feat(ui): redesign brute-force lockout timer alert --- app.py | 4 ++- templates/login.html | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 027b012..4f22dcc 100644 --- a/app.py +++ b/app.py @@ -394,7 +394,9 @@ def ratelimit_handler(e): """ if request.is_json or request.path.startswith('/api/'): return jsonify({"error": "Too many requests. Please wait 60 seconds."}), 429 - flash("Security Alert: Maximum login attempts exceeded.Please wait 60 seconds.", "error") + + flash("Security Alert: Maximum login attempts exceeded.", "error") + # We pass exactly 60 seconds to the UI so it knows how long to lock the form return render_template('login.html', lockout_seconds=60), 429 @app.route('/auth/google') diff --git a/templates/login.html b/templates/login.html index 7127bf7..ece2376 100644 --- a/templates/login.html +++ b/templates/login.html @@ -24,6 +24,14 @@ .password-toggle svg { width: 20px; height: 20px; fill: #595959; transition: fill 0.2s; } .password-toggle:hover svg { fill: #2d79f3; } + /* Modern Lockout Alert Styles */ + .lockout-alert { display: flex; align-items: center; gap: 15px; background-color: #fef2f2; border: 1px solid #fecaca; padding: 16px; border-radius: 12px; margin-bottom: 20px; } + .lockout-icon { width: 28px; height: 28px; color: #dc2626; flex-shrink: 0; } + .lockout-text { text-align: left; font-family: system-ui, -apple-system, sans-serif; } + .lockout-title { font-weight: 600; font-size: 15px; color: #991b1b; } + .lockout-desc { font-size: 14px; margin-top: 4px; color: #b91c1c; } + .lockout-timer-badge { font-weight: 700; background: #fee2e2; padding: 2px 8px; border-radius: 6px; color: #991b1b; } + [data-theme="dark"] .form { background-color: #1a1a1a; } [data-theme="dark"] .flex-column > label, [data-theme="dark"] .p, [data-theme="dark"] .flex-row > div > label { color: #f0f0f0; } [data-theme="dark"] .inputForm { border-color: #333; background: #252525; } @@ -31,6 +39,12 @@ [data-theme="dark"] .button-submit { background-color: #2d79f3; } [data-theme="dark"] .button-submit:hover { background-color: #1b5cc2; } [data-theme="dark"] .password-toggle svg { fill: #a0a0a0; } + + /* Dark Mode Lockout Alert */ + [data-theme="dark"] .lockout-alert { background-color: rgba(220, 38, 38, 0.1); border-color: rgba(220, 38, 38, 0.2); } + [data-theme="dark"] .lockout-title { color: #fca5a5; } + [data-theme="dark"] .lockout-desc { color: #f87171; } + [data-theme="dark"] .lockout-timer-badge { background: rgba(220, 38, 38, 0.25); color: #fff; } {% endblock %} @@ -41,6 +55,62 @@

Sign In

+ {% if lockout_seconds %} +
+ + + +
+
Brute Force Protection Active
+
Please wait {{ lockout_seconds }} seconds to try again.
+
+
+ + + {% endif %}