From c6dcca746369ee81cbe46f7e3f0173fbddb7e329 Mon Sep 17 00:00:00 2001 From: Leonardo Gentile Date: Wed, 1 Mar 2017 13:12:41 +0100 Subject: [PATCH] First commit --- .babelrc | 14 +++ .editorconfig | 23 ++++ .eslintrc.yml | 82 +++++++++++++++ .gitignore | 33 ++++++ .jshintrc | 3 + README.md | 99 ++++++++++++++++++ index.html | 16 +++ package.json | 64 +++++++++++ postcss.config.js | 19 ++++ src/actions/auth.js | 53 ++++++++++ src/app.jsx | 38 +++++++ src/components/Layout/Footer/Footer.jsx | 20 ++++ src/components/Layout/Footer/Footer.sass | 0 src/components/Layout/Header/Header.jsx | 35 +++++++ src/components/Layout/Header/Header.sass | 26 +++++ .../Layout/Header/NavAccount/NavAccount.jsx | 70 +++++++++++++ .../Layout/Header/NavAccount/NavAccount.sass | 42 ++++++++ .../Layout/Header/img/home_logo.jpg | Bin 0 -> 13732 bytes src/components/Layout/Layout.jsx | 42 ++++++++ src/components/Layout/Layout.sass | 9 ++ src/components/Layout/Sidebar/Sidebar.jsx | 14 +++ src/components/Layout/index.js | 3 + src/components/Main/Main.jsx | 31 ++++++ src/config.js | 11 ++ src/create-router5.js | 29 +++++ src/pages/Home.jsx | 17 +++ src/pages/Home.sass | 10 ++ src/pages/Index.jsx | 64 +++++++++++ src/pages/Index.sass | 10 ++ src/pages/Login.jsx | 95 +++++++++++++++++ src/routes.js | 5 + src/services/api.js | 1 + src/stores/TabStore.js | 48 +++++++++ src/stores/UserStore.js | 57 ++++++++++ src/stores/index.js | 8 ++ src/styles/abstracts/_mixins.sass | 31 ++++++ src/styles/abstracts/_variables.sass | 27 +++++ src/styles/base/_commons.sass | 13 +++ src/styles/base/_reset.sass | 61 +++++++++++ src/template.html | 15 +++ webpack.config.js | 64 +++++++++++ webpack.loaders.js | 97 +++++++++++++++++ webpack.production.config.js | 69 ++++++++++++ 43 files changed, 1468 insertions(+) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .eslintrc.yml create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 src/actions/auth.js create mode 100644 src/app.jsx create mode 100644 src/components/Layout/Footer/Footer.jsx create mode 100644 src/components/Layout/Footer/Footer.sass create mode 100644 src/components/Layout/Header/Header.jsx create mode 100644 src/components/Layout/Header/Header.sass create mode 100644 src/components/Layout/Header/NavAccount/NavAccount.jsx create mode 100644 src/components/Layout/Header/NavAccount/NavAccount.sass create mode 100644 src/components/Layout/Header/img/home_logo.jpg create mode 100644 src/components/Layout/Layout.jsx create mode 100644 src/components/Layout/Layout.sass create mode 100644 src/components/Layout/Sidebar/Sidebar.jsx create mode 100644 src/components/Layout/index.js create mode 100644 src/components/Main/Main.jsx create mode 100644 src/config.js create mode 100644 src/create-router5.js create mode 100644 src/pages/Home.jsx create mode 100644 src/pages/Home.sass create mode 100644 src/pages/Index.jsx create mode 100644 src/pages/Index.sass create mode 100644 src/pages/Login.jsx create mode 100644 src/routes.js create mode 100644 src/services/api.js create mode 100644 src/stores/TabStore.js create mode 100644 src/stores/UserStore.js create mode 100644 src/stores/index.js create mode 100644 src/styles/abstracts/_mixins.sass create mode 100644 src/styles/abstracts/_variables.sass create mode 100644 src/styles/base/_commons.sass create mode 100644 src/styles/base/_reset.sass create mode 100644 src/template.html create mode 100644 webpack.config.js create mode 100644 webpack.loaders.js create mode 100644 webpack.production.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..bb191b5 --- /dev/null +++ b/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "react", + "es2015", + "stage-3" + ], + "plugins": [ + "transform-runtime", + "transform-decorators-legacy", + "transform-class-properties", + "react-hot-loader/babel", + "react-html-attrs" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0511914 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,jsx,html}] +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Indent sass files with 4 spaces +[*.{sass}] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..dadeed3 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,82 @@ +--- + extends: + - plugin:react/recommended + + env: + browser: true + node: true + es6: true + + parserOptions: + ecmaVersion: 6 + sourceType: "module" + ecmaFeatures: + jsx: true + + globals: + __DEV__: true + __SERVER__: true + + plugins: + - react + + parser: "babel-eslint" + rules: + react/jsx-uses-vars: 1 + react/prop-types: [1, { ignore: [children] }] + + semi: 0 + key-spacing: 1 + curly: 0 + consistent-return: 0 + space-infix-ops: 1 + camelcase: 0 + no-spaced-func: 1 + no-alert: 1 + eol-last: 1 + comma-spacing: 1 + eqeqeq: 1 + + # possible errors + comma-dangle: 0 + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty-character-class: 2 + no-empty: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: 2 + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 2 + valid-typeof: 2 + + no-redeclare: 2 + + init-declarations: 2 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 2 + no-undef-init: 2 + no-undef: 2 + no-undefined: 2 + no-unused-vars: 2 + no-use-before-define: 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17628a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +# Ignore build files +public + +# Ignore my .mertrc file +.mertrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..2b6f469 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 6 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..333db29 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# react-webpack-babel-simple-starter +Simple React Webpack Babel Starter Kit + +Tired of complicated starters with 200MB of dependencies which are hard to understand and modify? + +Try this is a simple [React](https://facebook.github.io/react/), [Webpack](http://webpack.github.io/) and [Babel](https://babeljs.io/) application with nothing else in it. + +This is a fork of [react-webpack-babel-simple-starter](https://github.com/alicoding/react-webpack-babel) + +### What's in it? + +* Simple src/app.jsx and src/app.scss (local module css). +* Webpack configuration for development (with hot reloading) and production (with minification). +* CSS module loading, so you can include your css by ```import styles from './path/to.css';```. +* Both js(x) and css hot loaded during development. + +### To run + +* You'll need to have [git](https://git-scm.com/) and [node](https://nodejs.org/en/) installed in your system. +* Fork and clone the project: + +``` +git clone https://github.com/alicoding/react-webpack-babel.git +``` + +* Then install the dependencies: + +``` +npm install +``` + +* Run development server: + +``` +npm start +``` + +Open the web browser to `http://localhost:8888/` + +### To build the production package + +``` +npm run build +``` + +### Nginx Config + +Here is an example Nginx config: +``` +server { + # ... root and other options + + gzip on; + gzip_http_version 1.1; + gzip_types text/plain text/css text/xml application/javascript image/svg+xml; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~ \.html?$ { + expires 1d; + } + + location ~ \.(svg|ttf|js|css|svgz|eot|otf|woff|jpg|jpeg|gif|png|ico)$ { + access_log off; + log_not_found off; + expires max; + } +} +``` + +### Eslint +There is a .eslint.yaml config for eslint ready with React plugin. +To use it, you need to install additional dependencies though: + +``` +npm install --save-dev eslint eslint-plugin-react +``` + +To do the actual linting, run: + +``` +npm run lint +``` + +### Notes on importing css styles +* styles having /src/ in their absolute path are considered part of the application and exported as local css modules. +* other styles are considered global styles used by many components and are included in the css bundle directly. + +### Contribute +Please contribute to the project if you know how to make it better, including this README :) + +### Personal Setup +On the personal Setup branch I've added some tools and extra funcitonalities to better suit my needs. +- [babel-plugin-react-html-attrs](https://github.com/insin/babel-plugin-react-html-attrs) Transforms JSX `class` attributes into `className` and `for` attributes into `htmlFor`, allowing you to copy and paste HTML into your React components without having to manually edit these particular attributes each time. +- Personal `.editorconfig` setup +- Re-added `bootstrap` (it was stripped in the original project) +- [react-router](https://github.com/ReactTraining/react-router) basic setup diff --git a/index.html b/index.html new file mode 100644 index 0000000..b936a7c --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + React App + + + + +
+
Loading...
+
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f7ee4d --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "react-webpack-babel", + "version": "0.0.3", + "description": "React Webpack Babel Starter Kit", + "main": "''", + "scripts": { + "build": "webpack --config webpack.production.config.js --progress --profile --colors", + "start": "webpack-dev-server --progress --profile", + "lint": "eslint --ext js --ext jsx src || exit 0", + "dev": "webpack-dashboard -- webpack-dev-server --progress --profile --colors" + }, + "repository": { + "type": "git", + "url": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter" + }, + "author": "Leonardo Gentile", + "license": "MIT", + "homepage": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter#readme", + "dependencies": { + "axios": "^0.15.3", + "classnames": "^2.2.5", + "js-cookie": "^2.1.3", + "mobx": "^3.1.0", + "mobx-react": "^4.1.0", + "react": "15.4.1", + "react-dom": "15.4.1", + "react-router5": "^4.0.1", + "router5": "^4.5.1" + }, + "devDependencies": { + "autoprefixer": "^6.7.2", + "babel-core": "6.17.0", + "babel-eslint": "^6.1.2", + "babel-loader": "6.2.7", + "babel-plugin-react-html-attrs": "^2.0.0", + "babel-plugin-transform-class-properties": "^6.16.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-polyfill": "^6.16.0", + "babel-preset-es2015": "6.16.0", + "babel-preset-react": "6.16.0", + "babel-preset-stage-3": "^6.22.0", + "babel-runtime": "^6.11.6", + "css-loader": "0.26.0", + "eslint": "^3.16.0", + "eslint-plugin-react": "^6.10.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "0.9.0", + "html-webpack-plugin": "^2.22.0", + "lost": "^8.0.0", + "mobx-react-devtools": "^4.2.10", + "node-sass": "^3.10.1", + "postcss-loader": "^1.3.0", + "react-hot-loader": "^3.0.0-beta.6", + "sass-loader": "^4.0.2", + "sass-resources-loader": "^1.2.0", + "style-loader": "0.13.1", + "url-loader": "0.5.7", + "webpack": "1.13.2", + "webpack-cleanup-plugin": "^0.4.1", + "webpack-dashboard": "^0.3.0", + "webpack-dev-server": "1.16.3" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..0e1dcad --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,19 @@ +const AUTOPREFIXER_BROWSERS = [ + 'Android 2.3', + 'Android >= 4', + 'Chrome >= 35', + 'Firefox >= 31', + 'Explorer >= 9', + 'iOS >= 7', + 'Opera >= 12', + 'Safari >= 7.1', +]; + +module.exports = { + plugins: [ + require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }), + require('lost') + ] +} + + diff --git a/src/actions/auth.js b/src/actions/auth.js new file mode 100644 index 0000000..317a1d0 --- /dev/null +++ b/src/actions/auth.js @@ -0,0 +1,53 @@ +import axios from 'axios'; +import Cookies from 'js-cookie'; +import querystring from 'querystring'; +import userStore from '../stores/UserStore' + +// TODO: +function _handleErrors(response) { + if (response.data && response.data.success !== true) { + throw Error(response); + } + return response; +} + + +export function login(formData, callback=undefined){ + axios({ + method: 'post', + url: '/login', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: querystring.stringify(formData) + }) + .then(_handleErrors) + .then((response) => { + console.log(response); + userStore.setUser(response.data.data); + }) + .catch((error) => { + // optional callback + typeof callback == "function" && callback(error); + // TODO: _handleErrors(error, callback) + console.log(error); + }); +} + + + +export function logout(){ + axios.get("/logout") + .then((res) => { + + // TODO: + // - Remove cookies (also disclaimer) + // - Remove stored tabs + // - remove last finder link + // - remove watchlist + // - go to home page + + // or setUser(res.data.data) basically the same + userStore.resetUser(); + }) +} diff --git a/src/app.jsx b/src/app.jsx new file mode 100644 index 0000000..fe2f8a9 --- /dev/null +++ b/src/app.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'mobx-react'; +import { RouterProvider } from 'react-router5'; +import Layout from './components/Layout' + +// Local imports +import * as config from './config'; +import * as stores from './stores'; // mobx stores +import createRouter from './create-router5' // Router5 + +// ================= +// = Global Styles = +// ================= +import './styles/base/_reset.sass'; +import './styles/base/_commons.sass'; + +const router = createRouter(true); + +// RouterProvider will add your router instance in context. +const wrappedApp = ( + + + + + +); + +// Render the entire app when the router starts +router.start((err, state) => { + ReactDOM.render( + wrappedApp, + document.getElementById('app') + ); +}); + + + diff --git a/src/components/Layout/Footer/Footer.jsx b/src/components/Layout/Footer/Footer.jsx new file mode 100644 index 0000000..09b1e8c --- /dev/null +++ b/src/components/Layout/Footer/Footer.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import styles from './Footer.sass'; + +export default class Footer extends React.Component { + + static propTypes = { + name: React.PropTypes.string, + }; + + constructor(props) { + super(props); + } + + render() { + return ( + + ); + } +} diff --git a/src/components/Layout/Footer/Footer.sass b/src/components/Layout/Footer/Footer.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Layout/Header/Header.jsx b/src/components/Layout/Header/Header.jsx new file mode 100644 index 0000000..a86c9ad --- /dev/null +++ b/src/components/Layout/Header/Header.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import NavAccount from './NavAccount/NavAccount'; +import { BaseLink, withRoute } from 'react-router5'; + +import styles from './Header.sass'; +import logo_img from './img/home_logo.jpg' + +class Header extends React.Component { + + render() { + const { router } = this.props; + + return ( +
+
+ +
+ + Logo + +
+ +
+ +
+ +
+
+
+ ); + } +} + +// will pass the router trough context +export default withRoute(Header); diff --git a/src/components/Layout/Header/Header.sass b/src/components/Layout/Header/Header.sass new file mode 100644 index 0000000..a40b7ba --- /dev/null +++ b/src/components/Layout/Header/Header.sass @@ -0,0 +1,26 @@ + +$headerHeight: 126px + +.headerContainer + position: relative // so we can use absolute inside it + z-index: $zindex_headerContainer + height: $headerHeight + // **** + background: $orange + border-bottom: 2px solid $redSB-dark + border-top: 2px solid $redSB-dark + +.header + +container + +clearfix + position: relative + height: 100% + +.navAccountContainer + position: absolute + top: 0 + right: 0 + z-index: $zindex_navAccount + + + diff --git a/src/components/Layout/Header/NavAccount/NavAccount.jsx b/src/components/Layout/Header/NavAccount/NavAccount.jsx new file mode 100644 index 0000000..f24e716 --- /dev/null +++ b/src/components/Layout/Header/NavAccount/NavAccount.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { observer, inject } from 'mobx-react'; +import classNames from 'classnames'; +import { withRoute } from 'react-router5'; + +import styles from './NavAccount.sass'; + +@inject("userStore") +@observer +class NavAccount extends React.Component { + + constructor(props, context){ + super(props, context); + // this should work + // this.router = context.router; + this._showLogin = this._showLogin.bind(this); + } + + componentWillUnmount() { + console.log("Hello, I am the NavAccount, I will unmount") + } + + // =========== + // = Helpers = + // =========== + _showLogin(e) { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + + this.props.router.navigate('login', (err, state)=> { + if (err) console.log(err); + else console.log("I navigate to: " + state.name); + }); + } + + _renderMenu() { + const userStore = this.props.userStore; + + const isLoggedIn = userStore.isLoggedIn; + const ulClass = classNames(styles.navAccount, 'horizontal'); + + if (isLoggedIn){ + return ( + + ); + } + else { + return ( + + ); + } + + } + + render() { + return (this._renderMenu()) + } + +} + +export default withRoute(NavAccount); diff --git a/src/components/Layout/Header/NavAccount/NavAccount.sass b/src/components/Layout/Header/NavAccount/NavAccount.sass new file mode 100644 index 0000000..99a7f8f --- /dev/null +++ b/src/components/Layout/Header/NavAccount/NavAccount.sass @@ -0,0 +1,42 @@ + +.navAccount + > li + height: 46px + text-align: center + +light-on-dark + + > a + display: inline-block + color: #FFF + font-size: 15px + line-height: 44px + padding: 0 18px + + &.sign-in + background: $redSB-dark2 + + &.sign-up + background: $redSB-light + + &.builder + background: $redSB-light2 + + &.finder + background: $redSB + + &.my-account + //background: $redSB-dark url('/public/images/__menu_my_account.png') no-repeat 120px 0 + padding-right: 40px + position: relative + + &:hover + background-position: 120px -47px + + span + border-bottom: none + + .menu-hover + display: block + + a + padding-right: 0 diff --git a/src/components/Layout/Header/img/home_logo.jpg b/src/components/Layout/Header/img/home_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..175ef0dee358adf02a206d16b65c035fe7ca6f21 GIT binary patch literal 13732 zcmeHtcUV))*Y2icL8N!0fP&Jb_ryj~q$<5c1f&zHgc?LCQl%>@0RaI4k&c3d9*We^ zJE3<%4K2CxoO93jJ@>Eg-hc0J_LH5J$;`XwU9)Fq&00eoA9A zK>1IZAkz1rL_+>k`|t0@{3~`2R<70#uq&ch?7h6)6vf4Te0;?I$rQ7Oxc~q~2HAf- zHG}+L#MKOne`Vu8(8Squ!~cM|dWu=uz@FLuK@f*qxrs}N-4X|sA#gV5$J?5F+2+Rw>a!G;e4zM>3QguA%8*m_xA zfx9@ndMd(I`2GS{B*}lW#rdxMRm96lh0j<^_sTt(hwT*^F=??|e5BeQHqRCH@2mY& zm-I`8@4qqikrIP>*ojLhC@6^Ek`$Me6eX1q_4IZ1vVx1cdh-8og!{Ih)*cRSUJfwV zD}NBJp257mRQO0w{;Nqg*8ifqd3!kjg=u3gZtHC8V(aSVNun zzj65AMM;YJBmS#l@Rk3h|7hSp8u*U}{-c5aXy88@`2SA>|7D}ux{?etACd(I5H|of zU3-`p%+ns`c123;79fB3ffnT-(-HvWxXDZb3a)GF+$6J;93bZ=_X7C;2Ug497}x&7 zxSYxsSYel)D20W64?(THcP7>Y477kTuu4J30FX0~Q817Zn*k8XbfqHuEB#fCbR#3D zproRvp*?s00;xd7Wq_QFf`Xiqf{N--^T+~8{{xf^RE$?8?oc!7S<&#hGfTdV&!pwQ zThhRy-?uFw^~@vW+<8_ub`H*K*9C=c+?1A)m6KOcymwz!O4 znVr3Zqo?z->cW5Vc`*PA`=pml2cOOy-)j`m7Vh?H!uHdX<2ziWmWZ$pN&n; zEv@Lb_Ktqcz~IpE$mrNCc5Z%QacOyF6}Pjyw|{VmKRW)yi)3K`O^bB@o7w-ti=+fH za!N`HN}4~s$jE*E5NDvIx++1{O@B4K(ke@s4WHr<)j52(4f$ki zSfMDfn71D5Y$zGVsyvtj>pJHDR)V+9Z~Ho&mYnlLMPJ%M;}j0c@lBTcDuI7CJp*Tf ze|c?@-+(eqoh@FpgoPHAZw%#hD~(@7D56hZK-%#iJ0ll{Qn5CU$V)Pv#$%caMHPU; z*pC<@0C=1`RrS{IzP0-zd2TI#G92qHs_ys(je~|CNySx1nmJ^LEy{&xbn^&sJw&=W zVWRXOpv{o?wB7Srgp_heM*3=R6t8v&RA46gRIXKn^^sqEA<3SGt-YDq3&kmgyEv0p z`tutKlLw}#om1`f&e=fBu5Upza=n4<8H5p$=f}VbmSlc-}Hz8PA2YPjR+u}w(KCs!)Am)^)1ArQ4QYoE)n?bq?JBxUqy+S%Qk%5 z!$%Mb{5^zZSv%=~dJ+MpkDEm^Fou=Y{tNeOBM>ZQoAUcN-4G(f-rSvZMykRP6?!u| z{pH%MuvUYa|3lx>;->(yyHSy)-lN_Ei(O<;PpYz z$h9pk`Kq=u#zAcB-WN;&8CLUqk;r9}t3)7u=d_7H{)6DXG}v`(U~dui`Amlu#qiX@ zfpz{g1?f)&`28@m4#I@wB3Z=B)CBz*ZQ5MAFZ0<*;QH=OcS}@F&LL{2#5q7512j4Q zd;>eQ2AzmnpVdI-p>JMc3)N3g;^qjp{>7msuOZKea_Po(j4d2HLq9rkf#=qovap;mA&zX?3Zl!Ty6F#G)VG^-6h_|8@d1;HVRdu64akVObNc@r|UZrr_TxRtr<2 zhiYhA_Ne*Gn=jG4dGUJvL7U9#ICV(-5Lb48hM*0)yGuSZx} zF@Vyy|5BmfIu!AEd<_%D6BEnIHl<^GoDPPip~zfzx z_z)B2JGo1Y0ucqC?8`510gMVq#vUG$^enS;_{TI6ahq&qsXeKS6(>WJF_~{Z`V4|J zCN1&WOAjh=$@4$mOEul46<_1emwazifOHlGA!z5W>dnj`W!AAZ=@rT@re5Y(Wit+q zdT}W3pg!aG8_I0ig<0}!V&nO)s?0~)iNJWtnQhgiXIHv#Hp60W z>oiNPi&9JODuHhx*7ud#%aCt=^b^IMM1UODT8E^Mx7`Du7am+YTETPo7s=H~&*7bL z{cu7b5uhE8>GtiMtcJC9rEMDDyr5$TI;Dopt1E3jpPW8@x~)|HGZY=YcmFP#(|mH~ z!BI9nXw7`EG@8!Vp)cylY^Ox8;98lI4h5l)bFdJ12RnWt@L^xchXU!HT)nf6_cw{a zOPXHJqu&Uc-ZYQK_Lc*`Vw5-j)8&>20qW&-pJsI;6ji?b$~7-6(F)V7fhCXLS>R@9 zSf%B^8AulpSsc}Q^I@p*>Q6`)qP`D#FV#+FJN|6E7OGxxa)dCFQijeIQv}?q;0)?L zT(~D1`i@TN)v3OOyxt~y#X?@W81zvSxqj#;o{G5di=SxCao9CXOeEB4;1J9~y>++h zrlcdJC#gs4p1gp9brI76SL8R!2{QZD+Ls&j%L*r^C+Zf8v2l{d`M=5)PRvlzmNNDW zC5f%azdZtn$|7xE$62)Hy$+Ju)a!7QHpoRRN!diS$O zW1Z3HYTeoJ+@jY#rfWDye}kUrI?&kiW&Ea=rq+pQzjMfjF2@&sNR7cs3tGY4=ILVH zU4{_HfHXl`&kx&0m*gIGycT_>Z+_$!KxQz zY2t51x!>-4;2ExC~NO!1qYJ{N&}QqhQ)U!dLyzl>hUU9iQo zabnD*FknznZ>wff3GUZxU11*77^|bf<0*|d0`%U31Hr-zo?`T+OAQ@FU=oo<>Ng%~ zoW*rNov72Rxoat)aYeFM8;lvrj|6;;9;)48pA+!nFZSEMWfJpH>OelGa@Z^Ig&tD- z)sr=)CKL?M3w2+(fBE6}oAqx=3t%iQcz@4s>PvdwLuv%E@1OZm%s$PfyZCEEt||T0 z?;zZABzPC}6p~|SSZLocklwb`g0%|Gx;ZEdjasg=`-0zUHZ^J_0^zNTLa!6ll%u$w zjX1q;>k-5hNbFJ9`IEga_&DclRytYNdEDoVOrFD)73mv%b)4QmY0^`&dX^Fz8+D##irvnGxxhft(4FqwO34^BXdm9wqK0LP0Z9X^||0F(z;5(Eg=TiU=C@$E-v+( zA`wkg#x|h(UYbr+1)|>NBK|pP*|(&_WMPV(4ZeAE3ENy?8L+Taz0=sCh-@2?K6@O7vc=-){00FtqMt}1%5HrCw!fGguW&6oNh-@0$N z`On_gRUBWxmrMlgte)87I!*lcpyJ(2&4c+p!h7JBvxFd4L?hXZUV-D#xUy;yUzZpA z{J3f~8aSp%*?$&TFrhHIkwn&Ys)ch!&Opgl2{#Ac(Mq2@PrCZ_Vq70T2U?_=Y}1}% zm&OqFYp}xXmyRAnmT=%x{ln^1zlPP>pWIC)INM}jd|hF?M2v@U-RVVhws!Awd>fe| z{~Iy&)u=t|(cnf;!%5ru|g4;JM+{)$$6%^M{WsU$p)>X4H9lZsRsD z)wa(;8qO{nEV2^BJt)n?Dk^U9aU6Z1mfHQYQYma=LjLHM+F+~a#_ch1afX`Dn-szA z&FsWb39bkC!d9xRy6q`;c4wDD_hB6PDD~~37zY1wA|NK?^26B3UR2l)ujdP{y@ZW7 zDurkgIN?(2+ib6#m8Cwns{Z;Muv4ep`G$Tvxo!2ghnA%F+mE_59UyL$qs2W9oXS#o z9cXj~qiREVa-1_(ULcTlbx3F+*g%_lXX0b9e3x(x{{X@k$_+ ztK0PUrOCwzXwdm*+i|a%S`}LoiX*(M?+L!=U^F!az2|FKFsZgXtS2zs9#6QezAkw*kUJwj)G5bm`5BLv4lnqj$s6WIj#J6`q@S%a&lWI9L`&+(-zdIX#yz{uTZ;U2D?lOU=zwmdX9CR*&=mIX|v@^Mj zSbnEyIXFJCIl?e-J`FV1rAZ|;K`veCtK}o8)V=aPC`{M>*o~5Y@$fdKAIJGk@OYpQ z#fm9_6CbC*KB}X}yLolpT)y*dEvKl++@tp{&DNLJJG0uoh}yN=AnN5~a6!TEgE6th zJV(jK40dN1n`$`vHtlV9Ox+FEK!)wdXb49g6WqPrY5Mil)gdpc_qj3PR-BfzMd|EA z>WQ2;&X*+;yX7w?+BFk_O5U#}J$6}mGOx9H{X%c2vUkwRWxabKbH*PTjLLH}oA1A! zdYxs6?u)ZdwCAf{60f+yBf&Y|JpHpumBKOe?b+0}0 zZ%<3h(l%EOB5AE1uDy^ypJ9`T*QHssYejNToT}nu^5N-*u(C%}+DMWIBgOEmPD)nn z1&^gk+*slb&?7>9(B%`s(26*&?jM2ruo9DxONW;e*y9ef#gEQEvm0QhJ=#6k8qUHH zE|1#>+6;LMW!=04@n*FRI0*<2lBNTJgW8U*Cr|GV$Qa&ytb>UF*V%LfXZ(;)0SFGG z`G!WMiRN}B5eV^~wf3DZSp{)?YyP>BoK|(}Kb~r}xfIu@6|kv!$@G%N^n6f!DC)O_K@tc48aqAac&!}KxbRCkx6X;;cI7~`5rNYfQT*`lC7GkuubTD* z#87xW?z+_YvtJ2goB_wAO~Fo8%QQ<>^l4z86GNHi^P&SMX6*-|m)Kx6l{$4s{V2`$ zQX-J|bXN7$taFeEBw)6{*9(v4=L1(xq_tJpbI)#ASWq8>3CJ|`%WS_60Xv1ubmMl%6XpxV00)gwn5b1rc&Z4^nEa7-z z^V_Da?>9j=IZb}uYL*>fT| zfVqZ*>hgCZ?6-K3;nU~#T^uJmmEC44f^m%O;bua!^3!I z(;xZ5`Y2J~61_sRj+uetZ-W_E(hQn4(msvIjlI!TNVj~rRM%IbUK;tCUNeLK9T9NW z9IdsU)1e4p>)(`}aAIpD5whOpzR6Cm{*5wLrB|1x-&8g9TXN3WN{WPtY~|bRTdi4S zdba`kdR?j%d5#SFP-O=N{K|<@!{@-S27Ub1{-_3eXHhcCu=CAe8}lzJ11vDxVlloKDRRSHooiH{dFa>^_A<|n^)ABjc{ zb^$#ZLJYgp8|b;ji~yOq#uuLJC>+zfoUtI=GINf&dxzCJcpf0`O*x$y5wILC^a_-e zx^|H1$y4$3YvLR38D6qkHRFTEG}W^G3-#&TCEIbY%8_&qolzFDgQA$h%4k;b{5(Or zO}6JDfR{R$#JS_G!OC4-VVLl zV_i+=G4(cgvuqGA4JPr!3#VpL2ly>po{Xa1NrED8EURqj$;ZRUeb5G!5jstgHi!V3m4mM(nxO4|Z3z?L@*&0{x z2_i8v+xls_Y;u^#hmQNn=k>o5I$)B0%-H9v^GZL&h)1f^}4&VVrgQb=$%kDz8`*-urZ* zy85`sA`E3_F}fhN_w=#AT{o%QAmnsKRkN>hAdX>O8l9p=bJthYY4&UNpt)5mozH|?J+-d^Gk~+>hI={KUV0` zeAcBQO`}!lNLv){AaPbc8&>8Z3w3;d&&}@R+giVfLDbgINA_5V%@zRS2{0*5SQI_h z4l~lL+j~xP$dHbB)8KllzA1FiZW;dNfsW|_so$?}84P^0t}HDJU8+t~Yy2$K$*A)^ zgFYE$>y%%2o#fJx#OLIRWSET?vg%c1nUkcfQ!ybznk`fFsB-j;StwogFpQ&M?rgA&r0@b%n%nH9)i^qup4nFU-?1Dl0U1jN4i#6 z4nh4<%Ry#Q{`_U6Rm^XNAqv8_hQ4l?HrC?VXJEZc3o=ZoJL48PZhUzeI@T0uEAy!% zB@8~Mz$~HvB$^?rhZ zvscAFxqP+c0sB+GZD+QPAMc8D?+7NN@~rv-Y+hO#=X`VnmNRw-G!aXwdQ1iA^7XCU z28o|~pQVwUWZVIIKXI0CusmQcmq*R6SkwcKjgBiilj|oPx4Zk*&M6;cIHV75!)9A! zwmTbdWI8-LWUM(FPE8PEH1AN8yDU$NxLMYd-Hsg#25X*G*WJubBm(C(%|&1HTp@q2+*W}LtEN7w9p4h7m2{sm+c`@p;42I+M+Z5hd;wxH@Gy}4DZFWxa=sySOgoqlB3YtxuP=F zwXe1|_F^pBo?l^1&a;8p*aw`~>-97q0r*Ua(p>g=cw!0V~UEgqXaga3x^lz znyCeOB`UJN(Ymo2jC|l3-tHw4vl4qEtI12Lzd>8sM_T!#iVCS zz@+|oKH}LD^6>8PlCs77uY-n{KJlR&>ClTbZrX~RXJp(WCro@Vsnv@BgTZ|jhz7=Ppu%k2pG(+FT&ATtM1D?J z@_}_dNrmbgcDQF4EHu)k{EX27)Rbnb@%BLo$}zch_#MUR)gXhmC4oJ@L{9a7ROwQy zg&?|)xvbY)sm-O5BQHPYnW&6f!e zNKe_FMsJO2-q|k7=?*1D-vaHLDWzfV42-5D-{r5pbKAMpMo+VKS%_1;HSI+dR)^Jm zbu1?N(_+3%tq0VR>1=T+bZ7HGOaf+GGDYY_ zG0ouZaI_Pvh#m65OfwfX)%ix|l*p&RQAo;D#HjPZw`@)IW$nbg5yvr?x|}bTW>Y5S ztpXv~a};R4__rNRAvY4tGAQ_h6%Y|YkZokJS7JUq%Q&QkE8?@D7pCgaIy&HYP)IPC z{VXF$ehtbj7MgUcGY9T5rnNn?b#doAL?n@=?KxrfVkl(F*@`)S@nE9}ui0E#r8ecF zWQ4H`+hqO`ov*IXbAhb8UtWIo9Pj6{9Mezs`IrQ!pIln@J$+lRrkMg1aKcUQj;5*; zPJR(6*Y9?`-i7bxB@fN_tQvTP^)D^)k)p|A2b*IZLz)FO5k)i6uD-Tzd07LA^(2=X z!T~v^c40#iK^q{AvWuOOHaeaVeOZk@Odrm`z{b`iPLV`t0`p1)pzBk=FB(*0hu zSq_^elRfN@%i{RNKKe2B1_c((N)~bzKwyt;t3H5-uXGSf9*+-L&Lh|en#B{ow@9;J zJvSLtBXIfe0Nj#Qg2wX)nH~vh4SnI;-xa9q=(6|hwj`tN*f@l9GuKks{MeeI=O&iN zTFO0+`d-(TTeqovqPNVt8h#~C4=QE&DWLdrLGO#oo;^hB`=c)|Nj+3@Y=yi%$D>(G zDBjacxbX0A+10(XiBL=vTdbzDXb0dShmKDHP2C%QOBFa|(IT(>@L}13$L=^OLZ^O+ zlzU7szwuRQB)}ZsJ}Ycf24#gObjbH~l6;bjp}enqIGhpn17DH<+U z)6|1Ve0xx%{HeRbMrEf~utZPs<_N*K$QEAEsbg`jBr(thXNQ#;`*at?vbN6U$2GZj zp11j&?0tHb+g-l+=wX_0qXI+WC7(m#G7U`PC~x9S+~{y%5(eM-#D4QybcD;^VNU`Y zKXn)E0jUYD`%xT^#>Hf(FtTa-h}Avt+W&Q8yc)))vSqUa==d^9Z+LLaIQGCM>D)WZhbkDenFCC|nPg2)yQ1V#T?B zF5ui?J?_d&jVt_G>b}|f>)6wPO`2yo1(p1pvvRR(YeNV39DdOq|LSg~Kp?(+`7o%I z;n-lz0y!Zpwj}%&o{JrBep@;0mm7I{=VC@~BBP>NwTh33V>|-^$G`i{6yL$_HEEJJ zUIR@bh1v98rF1Nr%;=2nHgiVD_h4(@m786mIhMXyI`MsJVQxOI>ab>x$7xaOl6X_U zw!A@)*cK`H{o(Y|JF9GhD7}AQVk7VQ%JS`@k1k#d);ssPM1CV8)s#2EMFYdlL&m=w zIvMnL5JPW#U#ORbcRCGZuj;>YhYtVViHio2rys8d`ZSOWEqT~}c=Vk18#=+tx-r3b z4Bm6Mb{Jz*Yp~v*89i#BGd}3OZ+e_=U@o)&a>0*2Do7|ms5)V+Nq6W}_KQyX6!zk{ zl%2xZ47$hTHBo?!rN33~jR=d{I^wz;y-=z$H`~)F zegS@t&3s5KYoJ1&3|27#yfq^N!_vy6@Mow0aO0x+ov7c_-#`C&b}ULdjKLDWZ1vss zv0mh2qWGx>-hZ|@lGBFs)332Rc`Y287HSMN3u_B)wXWs|^86nej2C`X3SZA#X}1)I zqq@Lx+ksjV?`|}@vKWcP?{?R04`bCsWI$DBBaRB;5&NR7M$gFhB;s$L-BSNiT-V@E zO~LwWEK*pQ0bkRKXc!+sfIrB$*ip3IQn6SR z!GAs8&fqK$EIQS)po$F?>-6C&lEzmNxZWR~(kW?{v4%?pO|uZLo6}dtvA`q1?4oVn zRqQJ}-!;@1kR8<$94Sl}iwz5ny0dk{2#AhM^sU{7!!XAAbTVy%nf|40ftc@|d(zoS z$?18KNwfN5Oh!6cN-F$VwIb&N-b^Q|k@!2F~fP_g}f6>g`sivo{U+ zWC?;qhmVSzr^2z%VbRm= zBRjUMGAa!S6_Am^@gdDE8!`4)s1XQ7AbWU-jrP|l?fddfk?T!JT)U?krmY{pbSS>7 zxZ+k=e9}=c|KW7qW2`tPKpi1jjgpOs`C(mYkpc4XFvHD_{o*uv!I@ARbLOp53sw!u z1XKA)Qci5DuXv_MN%5}W-q)}Pa)pa1 zsnXQ*!-(d*)ST&4ig!Z+fmpgbrt&sXIuZMdi1e?asBsvjY21@f=FQd8h-e*f4+&pPaaUXvO|%5+jknXaf21Q28hs} z;VMl@mmAh&l|9C^Y%`9FCw|r)mNrqki882~f5D1k^fG&Q`b%BA=CuzOtIe6hnQ%wC zrlA<==n?-?fm`KuNK=w6KFE)ja24;az^Iu!#=5qrA01T^Dq;E9T;Aaf40`+ z+%W#x_Uc%r_hF=5F{rZesRunRd^9~PwELCDV1*_rOA*U zqM0wLUTv^;G^{F;Z~>oy%3PVn8{U}urFo=&I(@{gT~@Ga+KhF1xT6_z$v}q|xtTW; z=yx()tQMmvoRU_u_s*Q-2|xL_d@Ipf?ZS{Di}>huIH^yrltYi}SdF5VU8I zrCsjpD4dD??j@nOP{c)WA|3nC*ziKVIh4q=(0ufOvP-zM&Je6vk@mT}fDUWZNvv$y8v3Lg~@ z@Z*X@Tw>RpWDSYH7eCkOVe;@~>uWeoB5?hO#Oz0yeesDe3#?Aa>}&G(e!;h~VZH`z zJLYNq>FTGuq;CB*05{Ach~EFjg!mNSzLqXIyl-otU2`Hr*AO|vOzB@W1$kGj9|pY} tRuVJqlfOAD`G}V#UuYM+1#N=;UJ3@D0yG)lnubXS1c40lU +
+ +
+
+
+ +
+
+
+ +