aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
committerFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
commit3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b (patch)
treef101656799da807489253e17bea7abfaea90b62d /packages/website
parent037f466e1f80f635b48f3235258402e2ce75fb7b (diff)
downloaddexon-0x-contracts-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.gz
dexon-0x-contracts-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.zst
dexon-0x-contracts-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.zip
Add website to mono repo, update packages to align with existing sub-packages, use new subscribeAsync 0x.js method
Diffstat (limited to 'packages/website')
-rw-r--r--packages/website/README.md60
-rw-r--r--packages/website/contracts/Mintable.json189
-rw-r--r--packages/website/less/all.less136
-rw-r--r--packages/website/package.json105
-rw-r--r--packages/website/public/css/atom-one-light.css96
-rw-r--r--packages/website/public/css/basscss_responsive_custom.css58
-rw-r--r--packages/website/public/css/basscss_responsive_margin.css160
-rw-r--r--packages/website/public/css/basscss_responsive_padding.css134
-rw-r--r--packages/website/public/css/basscss_responsive_type_scale.css35
-rwxr-xr-xpackages/website/public/css/material-design-iconic-font.css5166
-rwxr-xr-xpackages/website/public/css/material-design-iconic-font.min.css1
-rw-r--r--packages/website/public/css/roboto.css83
-rw-r--r--packages/website/public/css/roboto_mono.css69
-rwxr-xr-xpackages/website/public/fonts/Material-Design-Iconic-Font.eotbin0 -> 42495 bytes
-rwxr-xr-xpackages/website/public/fonts/Material-Design-Iconic-Font.svg787
-rwxr-xr-xpackages/website/public/fonts/Material-Design-Iconic-Font.ttfbin0 -> 99212 bytes
-rwxr-xr-xpackages/website/public/fonts/Material-Design-Iconic-Font.woffbin0 -> 50312 bytes
-rwxr-xr-xpackages/website/public/fonts/Material-Design-Iconic-Font.woff2bin0 -> 38384 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Black.ttfbin0 -> 171480 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-BlackItalic.ttfbin0 -> 177552 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Bold.ttfbin0 -> 170760 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-BoldItalic.ttfbin0 -> 174952 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Italic.ttfbin0 -> 173932 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Light.ttfbin0 -> 170420 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-LightItalic.ttfbin0 -> 176616 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Medium.ttfbin0 -> 172064 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-MediumItalic.ttfbin0 -> 176864 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Regular.ttfbin0 -> 171676 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-Thin.ttfbin0 -> 171904 bytes
-rwxr-xr-xpackages/website/public/fonts/Roboto-ThinItalic.ttfbin0 -> 176300 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Bold.ttfbin0 -> 114752 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-BoldItalic.ttfbin0 -> 122808 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Italic.ttfbin0 -> 120832 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Light.ttfbin0 -> 118976 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-LightItalic.ttfbin0 -> 127568 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Medium.ttfbin0 -> 114696 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-MediumItalic.ttfbin0 -> 123640 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Regular.ttfbin0 -> 114624 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-Thin.ttfbin0 -> 118132 bytes
-rwxr-xr-xpackages/website/public/fonts/RobotoMono-ThinItalic.ttfbin0 -> 121456 bytes
-rw-r--r--packages/website/public/gifs/0xAnimation.gifbin0 -> 909585 bytes
-rw-r--r--packages/website/public/gifs/genesis.gifbin0 -> 735849 bytes
-rw-r--r--packages/website/public/images/0x_logo.pngbin0 -> 64503 bytes
-rw-r--r--packages/website/public/images/advisors/fred.jpgbin0 -> 4619 bytes
-rw-r--r--packages/website/public/images/advisors/joey.jpgbin0 -> 13493 bytes
-rw-r--r--packages/website/public/images/advisors/linda.jpgbin0 -> 6580 bytes
-rw-r--r--packages/website/public/images/advisors/olaf.pngbin0 -> 26365 bytes
-rw-r--r--packages/website/public/images/ether.pngbin0 -> 2312 bytes
-rwxr-xr-xpackages/website/public/images/favicon/favicon-2-16x16.pngbin0 -> 684 bytes
-rwxr-xr-xpackages/website/public/images/favicon/favicon-2-32x32.pngbin0 -> 1567 bytes
-rwxr-xr-xpackages/website/public/images/favicon/favicon.icobin0 -> 5430 bytes
-rw-r--r--packages/website/public/images/landing/0x_chips.pngbin0 -> 170875 bytes
-rw-r--r--packages/website/public/images/landing/aragon.pngbin0 -> 4738 bytes
-rw-r--r--packages/website/public/images/landing/augur.pngbin0 -> 4935 bytes
-rw-r--r--packages/website/public/images/landing/currency.pngbin0 -> 3348 bytes
-rw-r--r--packages/website/public/images/landing/dharma.pngbin0 -> 4754 bytes
-rw-r--r--packages/website/public/images/landing/digital_goods.pngbin0 -> 3880 bytes
-rw-r--r--packages/website/public/images/landing/distributed_network.pngbin0 -> 37483 bytes
-rw-r--r--packages/website/public/images/landing/ethfinex.pngbin0 -> 6733 bytes
-rw-r--r--packages/website/public/images/landing/fund_management_icon.pngbin0 -> 5552 bytes
-rw-r--r--packages/website/public/images/landing/gnosis.pngbin0 -> 4888 bytes
-rw-r--r--packages/website/public/images/landing/governance_icon.pngbin0 -> 6230 bytes
-rw-r--r--packages/website/public/images/landing/hero_chip_image.pngbin0 -> 256493 bytes
-rw-r--r--packages/website/public/images/landing/lendroid.pngbin0 -> 4305 bytes
-rw-r--r--packages/website/public/images/landing/liquidity.pngbin0 -> 22140 bytes
-rw-r--r--packages/website/public/images/landing/loans_icon.pngbin0 -> 5900 bytes
-rw-r--r--packages/website/public/images/landing/maker.pngbin0 -> 3501 bytes
-rw-r--r--packages/website/public/images/landing/melonport.pngbin0 -> 4841 bytes
-rw-r--r--packages/website/public/images/landing/open_source.pngbin0 -> 14696 bytes
-rw-r--r--packages/website/public/images/landing/paradex.pngbin0 -> 6904 bytes
-rw-r--r--packages/website/public/images/landing/prediction_market_icon.pngbin0 -> 6211 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/anx.pngbin0 -> 5836 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/aragon.pngbin0 -> 4642 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/auctus.pngbin0 -> 3751 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/augur.pngbin0 -> 4618 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/blocknet.pngbin0 -> 4697 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/chronobank.pngbin0 -> 6209 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/dharma.pngbin0 -> 5429 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/district0x.pngbin0 -> 5515 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/dydx.pngbin0 -> 4191 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/ethfinex-top.pngbin0 -> 4376 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/ethix.pngbin0 -> 3438 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/lendroid.pngbin0 -> 4866 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/maker.pngbin0 -> 3951 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/melonport.pngbin0 -> 5186 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/paradex_top.pngbin0 -> 5109 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/radar_relay_top.pngbin0 -> 4898 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/status.pngbin0 -> 4287 bytes
-rw-r--r--packages/website/public/images/landing/project_logos/the_ocean.pngbin0 -> 4766 bytes
-rw-r--r--packages/website/public/images/landing/radar_relay.pngbin0 -> 6650 bytes
-rw-r--r--packages/website/public/images/landing/relayer_diagram.pngbin0 -> 111870 bytes
-rw-r--r--packages/website/public/images/landing/stable_tokens_icon.pngbin0 -> 5853 bytes
-rw-r--r--packages/website/public/images/landing/stocks.pngbin0 -> 2098 bytes
-rw-r--r--packages/website/public/images/landing/tokenized_world.pngbin0 -> 109220 bytes
-rw-r--r--packages/website/public/images/loading_poster.pngbin0 -> 2505 bytes
-rw-r--r--packages/website/public/images/logos/FBG.pngbin0 -> 73781 bytes
-rw-r--r--packages/website/public/images/logos/aragon.pngbin0 -> 5501 bytes
-rw-r--r--packages/website/public/images/logos/augur.pngbin0 -> 5051 bytes
-rw-r--r--packages/website/public/images/logos/blockchain_capital.pngbin0 -> 12366 bytes
-rw-r--r--packages/website/public/images/logos/chronobank.pngbin0 -> 5615 bytes
-rw-r--r--packages/website/public/images/logos/dharma.pngbin0 -> 5015 bytes
-rw-r--r--packages/website/public/images/logos/district0x.pngbin0 -> 5537 bytes
-rw-r--r--packages/website/public/images/logos/jen_advisors.pngbin0 -> 158434 bytes
-rw-r--r--packages/website/public/images/logos/maker.pngbin0 -> 3791 bytes
-rw-r--r--packages/website/public/images/logos/melonport.pngbin0 -> 5218 bytes
-rw-r--r--packages/website/public/images/logos/openANX.pngbin0 -> 4973 bytes
-rw-r--r--packages/website/public/images/logos/pantera_capital.pngbin0 -> 8437 bytes
-rw-r--r--packages/website/public/images/logos/polychain_capital.pngbin0 -> 21279 bytes
-rw-r--r--packages/website/public/images/og_image.pngbin0 -> 192500 bytes
-rw-r--r--packages/website/public/images/protocol_logo_black.pngbin0 -> 4031 bytes
-rw-r--r--packages/website/public/images/protocol_logo_white.pngbin0 -> 3931 bytes
-rw-r--r--packages/website/public/images/social/github.pngbin0 -> 1154 bytes
-rw-r--r--packages/website/public/images/social/medium.pngbin0 -> 890 bytes
-rw-r--r--packages/website/public/images/social/reddit.pngbin0 -> 1168 bytes
-rw-r--r--packages/website/public/images/social/rocketchat.pngbin0 -> 1587 bytes
-rw-r--r--packages/website/public/images/social/slack.pngbin0 -> 1311 bytes
-rw-r--r--packages/website/public/images/social/twitter.pngbin0 -> 901 bytes
-rw-r--r--packages/website/public/images/team/alex.jpgbin0 -> 7271 bytes
-rw-r--r--packages/website/public/images/team/amir.jpegbin0 -> 21098 bytes
-rw-r--r--packages/website/public/images/team/anyone.pngbin0 -> 4794 bytes
-rw-r--r--packages/website/public/images/team/ben.jpgbin0 -> 25486 bytes
-rw-r--r--packages/website/public/images/team/brandon.pngbin0 -> 30180 bytes
-rw-r--r--packages/website/public/images/team/fabio.jpgbin0 -> 17125 bytes
-rw-r--r--packages/website/public/images/team/leonid.pngbin0 -> 35014 bytes
-rw-r--r--packages/website/public/images/team/philippe.pngbin0 -> 51419 bytes
-rw-r--r--packages/website/public/images/team/will.jpgbin0 -> 11493 bytes
-rw-r--r--packages/website/public/images/token_icons/adtoken.pngbin0 -> 5150 bytes
-rw-r--r--packages/website/public/images/token_icons/aragon.pngbin0 -> 14012 bytes
-rw-r--r--packages/website/public/images/token_icons/augur.pngbin0 -> 146258 bytes
-rw-r--r--packages/website/public/images/token_icons/bancor.pngbin0 -> 2274 bytes
-rw-r--r--packages/website/public/images/token_icons/basicattentiontoken.pngbin0 -> 7082 bytes
-rw-r--r--packages/website/public/images/token_icons/bitquence.pngbin0 -> 14293 bytes
-rw-r--r--packages/website/public/images/token_icons/btc.pngbin0 -> 5742 bytes
-rw-r--r--packages/website/public/images/token_icons/civic.pngbin0 -> 10700 bytes
-rw-r--r--packages/website/public/images/token_icons/clams.pngbin0 -> 18866 bytes
-rw-r--r--packages/website/public/images/token_icons/cofound-it.pngbin0 -> 5403 bytes
-rw-r--r--packages/website/public/images/token_icons/default.pngbin0 -> 17037 bytes
-rw-r--r--packages/website/public/images/token_icons/digixdao.pngbin0 -> 34397 bytes
-rw-r--r--packages/website/public/images/token_icons/district0x.pngbin0 -> 67267 bytes
-rw-r--r--packages/website/public/images/token_icons/edgeless.pngbin0 -> 2712 bytes
-rw-r--r--packages/website/public/images/token_icons/eos.pngbin0 -> 2168 bytes
-rw-r--r--packages/website/public/images/token_icons/ether_erc20.pngbin0 -> 69772 bytes
-rw-r--r--packages/website/public/images/token_icons/etheroll.pngbin0 -> 22736 bytes
-rw-r--r--packages/website/public/images/token_icons/firstblood.jpgbin0 -> 72909 bytes
-rw-r--r--packages/website/public/images/token_icons/funfair.pngbin0 -> 18800 bytes
-rw-r--r--packages/website/public/images/token_icons/gnosis.pngbin0 -> 7554 bytes
-rw-r--r--packages/website/public/images/token_icons/golem.pngbin0 -> 2990 bytes
-rw-r--r--packages/website/public/images/token_icons/iconomi.pngbin0 -> 810 bytes
-rw-r--r--packages/website/public/images/token_icons/iexec.pngbin0 -> 1910 bytes
-rw-r--r--packages/website/public/images/token_icons/lunyr.pngbin0 -> 5734 bytes
-rw-r--r--packages/website/public/images/token_icons/makerdao.pngbin0 -> 3161 bytes
-rw-r--r--packages/website/public/images/token_icons/melon.pngbin0 -> 3408 bytes
-rw-r--r--packages/website/public/images/token_icons/metal.pngbin0 -> 1295 bytes
-rw-r--r--packages/website/public/images/token_icons/monaco.pngbin0 -> 6984 bytes
-rw-r--r--packages/website/public/images/token_icons/numeraire.pngbin0 -> 10272 bytes
-rw-r--r--packages/website/public/images/token_icons/omisego.pngbin0 -> 5976 bytes
-rw-r--r--packages/website/public/images/token_icons/qtum.pngbin0 -> 169182 bytes
-rw-r--r--packages/website/public/images/token_icons/santiment.pngbin0 -> 4698 bytes
-rw-r--r--packages/website/public/images/token_icons/singularity.pngbin0 -> 3849 bytes
-rw-r--r--packages/website/public/images/token_icons/status.pngbin0 -> 3445 bytes
-rw-r--r--packages/website/public/images/token_icons/storjcoinx.pngbin0 -> 9453 bytes
-rw-r--r--packages/website/public/images/token_icons/taas.pngbin0 -> 7823 bytes
-rw-r--r--packages/website/public/images/token_icons/tenx.pngbin0 -> 7276 bytes
-rw-r--r--packages/website/public/images/token_icons/tokencard.pngbin0 -> 14586 bytes
-rw-r--r--packages/website/public/images/token_icons/trust.pngbin0 -> 14428 bytes
-rw-r--r--packages/website/public/images/token_icons/wings.pngbin0 -> 16143 bytes
-rw-r--r--packages/website/public/images/token_icons/zero_ex.pngbin0 -> 162879 bytes
-rw-r--r--packages/website/public/images/trade_arrows.pngbin0 -> 1740 bytes
-rw-r--r--packages/website/public/images/zrx_pie_chart.pngbin0 -> 54185 bytes
-rw-r--r--packages/website/public/images/zrx_token.pngbin0 -> 16534 bytes
-rw-r--r--packages/website/public/index.html78
-rw-r--r--packages/website/public/js/rollbar.umd.nojson.min.js1
-rw-r--r--packages/website/public/pdfs/0x_white_paper.pdfbin0 -> 319570 bytes
-rw-r--r--packages/website/public/videos/0xAnimation.mp4bin0 -> 970342 bytes
-rw-r--r--packages/website/ts/blockchain.ts770
-rw-r--r--packages/website/ts/components/dialogs/blockchain_err_dialog.tsx158
-rw-r--r--packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx139
-rw-r--r--packages/website/ts/components/dialogs/ledger_config_dialog.tsx288
-rw-r--r--packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx44
-rw-r--r--packages/website/ts/components/dialogs/send_dialog.tsx126
-rw-r--r--packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx99
-rw-r--r--packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx53
-rw-r--r--packages/website/ts/components/eth_weth_conversion_button.tsx101
-rw-r--r--packages/website/ts/components/fill_order.tsx714
-rw-r--r--packages/website/ts/components/fill_order_json.tsx69
-rw-r--r--packages/website/ts/components/fill_warning_dialog.tsx47
-rw-r--r--packages/website/ts/components/flash_messages/token_send_completed.tsx38
-rw-r--r--packages/website/ts/components/flash_messages/transaction_submitted.tsx29
-rw-r--r--packages/website/ts/components/footer.tsx255
-rw-r--r--packages/website/ts/components/generate_order/asset_picker.tsx291
-rw-r--r--packages/website/ts/components/generate_order/generate_order_form.tsx348
-rw-r--r--packages/website/ts/components/generate_order/new_token_form.tsx237
-rw-r--r--packages/website/ts/components/inputs/address_input.tsx74
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx94
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx160
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx51
-rw-r--r--packages/website/ts/components/inputs/expiration_input.tsx108
-rw-r--r--packages/website/ts/components/inputs/hash_input.tsx65
-rw-r--r--packages/website/ts/components/inputs/identicon_address_input.tsx56
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx69
-rw-r--r--packages/website/ts/components/inputs/token_input.tsx107
-rw-r--r--packages/website/ts/components/order_json.tsx164
-rw-r--r--packages/website/ts/components/portal.tsx344
-rw-r--r--packages/website/ts/components/portal_menu.tsx68
-rw-r--r--packages/website/ts/components/send_button.tsx89
-rw-r--r--packages/website/ts/components/token_balances.tsx697
-rw-r--r--packages/website/ts/components/top_bar.tsx370
-rw-r--r--packages/website/ts/components/top_bar_menu_item.tsx53
-rw-r--r--packages/website/ts/components/track_token_confirmation.tsx65
-rw-r--r--packages/website/ts/components/trade_history/trade_history.tsx115
-rw-r--r--packages/website/ts/components/trade_history/trade_history_item.tsx178
-rw-r--r--packages/website/ts/components/ui/alert.tsx27
-rw-r--r--packages/website/ts/components/ui/badge.tsx58
-rw-r--r--packages/website/ts/components/ui/copy_icon.tsx81
-rw-r--r--packages/website/ts/components/ui/drop_down_menu_item.tsx117
-rw-r--r--packages/website/ts/components/ui/ethereum_address.tsx35
-rw-r--r--packages/website/ts/components/ui/etherscan_icon.tsx50
-rw-r--r--packages/website/ts/components/ui/fake_text_field.tsx35
-rw-r--r--packages/website/ts/components/ui/flash_message.tsx40
-rw-r--r--packages/website/ts/components/ui/help_tooltip.tsx22
-rw-r--r--packages/website/ts/components/ui/identicon.tsx36
-rw-r--r--packages/website/ts/components/ui/input_label.tsx27
-rw-r--r--packages/website/ts/components/ui/labeled_switcher.tsx76
-rw-r--r--packages/website/ts/components/ui/lifecycle_raised_button.tsx105
-rw-r--r--packages/website/ts/components/ui/loading.tsx36
-rw-r--r--packages/website/ts/components/ui/menu_item.tsx54
-rw-r--r--packages/website/ts/components/ui/party.tsx150
-rw-r--r--packages/website/ts/components/ui/required_label.tsx15
-rw-r--r--packages/website/ts/components/ui/simple_loading.tsx23
-rw-r--r--packages/website/ts/components/ui/swap_icon.tsx46
-rw-r--r--packages/website/ts/components/ui/token_icon.tsx29
-rw-r--r--packages/website/ts/components/visual_order.tsx77
-rw-r--r--packages/website/ts/containers/generate_order_form.tsx54
-rw-r--r--packages/website/ts/containers/portal.tsx94
-rw-r--r--packages/website/ts/containers/smart_contracts_documentation.tsx31
-rw-r--r--packages/website/ts/containers/zero_ex_js_documentation.tsx33
-rw-r--r--packages/website/ts/globals.d.ts154
-rw-r--r--packages/website/ts/index.tsx116
-rw-r--r--packages/website/ts/lazy_component.tsx66
-rw-r--r--packages/website/ts/local_storage/local_storage.ts35
-rw-r--r--packages/website/ts/local_storage/tracked_token_storage.ts56
-rw-r--r--packages/website/ts/local_storage/trade_history_storage.tsx82
-rw-r--r--packages/website/ts/pages/about/about.tsx253
-rw-r--r--packages/website/ts/pages/about/profile.tsx99
-rw-r--r--packages/website/ts/pages/documentation/comment.tsx24
-rw-r--r--packages/website/ts/pages/documentation/custom_enum.tsx31
-rw-r--r--packages/website/ts/pages/documentation/enum.tsx26
-rw-r--r--packages/website/ts/pages/documentation/event_definition.tsx80
-rw-r--r--packages/website/ts/pages/documentation/interface.tsx54
-rw-r--r--packages/website/ts/pages/documentation/method_block.tsx174
-rw-r--r--packages/website/ts/pages/documentation/method_signature.tsx62
-rw-r--r--packages/website/ts/pages/documentation/smart_contracts_documentation.tsx401
-rw-r--r--packages/website/ts/pages/documentation/source_link.tsx27
-rw-r--r--packages/website/ts/pages/documentation/type.tsx187
-rw-r--r--packages/website/ts/pages/documentation/type_definition.tsx135
-rw-r--r--packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx340
-rw-r--r--packages/website/ts/pages/faq/faq.tsx497
-rw-r--r--packages/website/ts/pages/faq/question.tsx52
-rw-r--r--packages/website/ts/pages/landing/landing.tsx843
-rw-r--r--packages/website/ts/pages/not_found.tsx46
-rw-r--r--packages/website/ts/pages/shared/anchor_title.tsx98
-rw-r--r--packages/website/ts/pages/shared/markdown_code_block.tsx20
-rw-r--r--packages/website/ts/pages/shared/markdown_section.tsx77
-rw-r--r--packages/website/ts/pages/shared/nested_sidebar_menu.tsx163
-rw-r--r--packages/website/ts/pages/shared/section_header.tsx50
-rw-r--r--packages/website/ts/pages/shared/version_drop_down.tsx46
-rw-r--r--packages/website/ts/pages/wiki/wiki.tsx210
-rw-r--r--packages/website/ts/redux/dispatcher.ts244
-rw-r--r--packages/website/ts/redux/reducer.ts363
-rw-r--r--packages/website/ts/schemas/order_schema.ts24
-rw-r--r--packages/website/ts/schemas/order_taker_schema.ts11
-rw-r--r--packages/website/ts/schemas/signature_data_schema.ts11
-rw-r--r--packages/website/ts/schemas/token_schema.ts11
-rw-r--r--packages/website/ts/schemas/validator.ts19
-rw-r--r--packages/website/ts/subproviders/injected_web3_subprovider.ts44
-rw-r--r--packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts172
-rw-r--r--packages/website/ts/subproviders/redundant_rpc_subprovider.ts41
-rw-r--r--packages/website/ts/types.ts692
-rw-r--r--packages/website/ts/utils/configs.ts18
-rw-r--r--packages/website/ts/utils/constants.ts270
-rw-r--r--packages/website/ts/utils/doc_utils.ts55
-rw-r--r--packages/website/ts/utils/doxity_utils.ts162
-rw-r--r--packages/website/ts/utils/error_reporter.ts52
-rw-r--r--packages/website/ts/utils/typedoc_utils.ts356
-rw-r--r--packages/website/ts/utils/utils.ts215
-rw-r--r--packages/website/ts/vendor/u2f_api.js760
-rw-r--r--packages/website/ts/web3_wrapper.ts146
-rw-r--r--packages/website/tsconfig.json20
-rw-r--r--packages/website/tslint.json9
-rw-r--r--packages/website/webpack.config.js86
290 files changed, 23527 insertions, 0 deletions
diff --git a/packages/website/README.md b/packages/website/README.md
new file mode 100644
index 000000000..40df07cb7
--- /dev/null
+++ b/packages/website/README.md
@@ -0,0 +1,60 @@
+<img src="https://github.com/0xProject/branding/blob/master/0x_Black_CMYK.png" width="200px" >
+
+---
+
+[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. A full description of the protocol may be found in our [whitepaper][whitepaper-url].
+
+This repository contains our website and [0x Portal DApp][portal-url] (over-the-counter exchange), facilitating trustless over-the-counter trading of Ethereum-based tokens using 0x protocol.
+
+[website-url]: https://0xproject.com/
+[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
+[portal-url]: https://0xproject.com/portal
+
+[![Join the chat at https://gitter.im/0xProject/contracts](https://badges.gitter.im/0xProject/contracts.svg)](https://gitter.im/0xProject/contracts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+
+### Local Dev Setup
+
+Requires Node version 6.9.5 or higher.
+
+Add the following to your `/etc/hosts` file:
+
+```
+127.0.0.1 0xproject.dev
+```
+
+Clone the [0x contracts repo](https://github.com/0xProject/contracts) into the same parent directory as this project.
+
+Install [yarn](https://yarnpkg.com/lang/en/docs/install/) in order to install the project dependencies more deterministically.
+
+Install dependencies:
+
+```
+yarn
+```
+
+Import smart contract artifacts from `contracts` repo:
+
+```
+yarn run update_contracts
+```
+
+Start dev server:
+
+```
+yarn run dev
+```
+
+Visit [0xproject.dev:3572](http://0xproject.dev:3572) in your browser.
+
+
+##### Recommended Atom packages:
+
+- [atom-typescript](https://atom.io/packages/atom-typescript)
+- [linter-tslint](https://atom.io/packages/linter-tslint)
+
+##### Resources
+
+- [Material Design Icon Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html#directional)
+- [BassCSS toolkit](http://basscss.com/)
+- [Material-UI](http://www.material-ui.com/#/)
diff --git a/packages/website/contracts/Mintable.json b/packages/website/contracts/Mintable.json
new file mode 100644
index 000000000..52dc7507f
--- /dev/null
+++ b/packages/website/contracts/Mintable.json
@@ -0,0 +1,189 @@
+{
+ "contract_name": "Mintable",
+ "abi": [
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "mint",
+ "outputs": [],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "name": "_spender",
+ "type": "address"
+ }
+ ],
+ "name": "allowance",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ }
+ ],
+ "unlinked_binary": "0x6060604052341561000c57fe5b5b6105018061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a0360043516610335565b60408051918252519081900360200190f35b341561012d57fe5b610138600435610354565b005b341561014257fe5b610088600160a060020a03600435166024356103bc565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a036004358116906024351661046e565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906102555750828110155b801561027b5750600160a060020a03841660009081526020819052604090205483810110155b1561032757600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156102e557600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206104b6833981519152856040518082815260200191505060405180910390a36001915061032c565b600091505b5b509392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561036b5760006000fd5b600160a060020a03331660009081526020819052604090205461038f90829061049b565b600160a060020a0333166000908152602081905260409020556002546103b5908261049b565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103ff5750600160a060020a03831660009081526020819052604090205482810110155b1561045f57600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206104b6833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156104aa57fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820998c8326b9629e063eb4867166e72c68a8c2e3ebca6a9d35ebc78c041c7aa47b0029",
+ "networks": {},
+ "schema_version": "0.0.5",
+ "updated_at": 1503413048892
+} \ No newline at end of file
diff --git a/packages/website/less/all.less b/packages/website/less/all.less
new file mode 100644
index 000000000..5212df959
--- /dev/null
+++ b/packages/website/less/all.less
@@ -0,0 +1,136 @@
+body {
+ font-family: 'Roboto';
+}
+
+.robotoMono {
+ font-family: 'Roboto Mono';
+}
+
+a {
+ color: black;
+}
+
+#faq {
+ li {
+ padding-bottom: 5px;
+ }
+
+ a {
+ color: rgb(66, 66, 66);
+ }
+}
+
+#landing {
+ .h1, .h2, .h3, .h4 {
+ font-family: 'Roboto Mono';
+ }
+}
+
+
+#portal {
+ h1, h2, h3, h4 {
+ font-weight: 100;
+ }
+}
+
+#documentation {
+ p {
+ line-height: 1.5;
+ }
+
+ .comment {
+ p {
+ margin: 0px;
+ }
+ }
+
+ .typeTooltip {
+ border: 1px solid lightgray;
+ opacity: 1;
+ }
+}
+
+/*
+ * Adds always visible scrollbars on OSX so that user knows the content is scrollable
+ * Source: https://davidwalsh.name/osx-overflow
+ */
+::-webkit-scrollbar {
+ -webkit-appearance: none;
+ width: 7px;
+}
+::-webkit-scrollbar-thumb {
+ border-radius: 4px;
+ background-color: rgba(0, 0, 0, .5);
+ -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5);
+}
+
+// Hack: For some reason the animation applied to the material-ui textfield causes the overflow
+// applied to other elements to fail while the animation is underway. Adding this class to the
+// affected component fixes the issue
+// Source: http://stackoverflow.com/questions/14383632/webkit-border-radius-and-overflow-bug-when-using-any-animation-transition
+.transitionFix {
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
+}
+
+.thin {
+ font-weight: 100;
+}
+
+code {
+ font-family: 'Roboto';
+ background-color: #f3f4f4;
+ color: rgb(36, 41, 46);
+ padding: 3px;
+
+ &.hljs {
+ background-color: #dde4e9 !important; // blue gray
+ border-left: 5px solid #0091EA !important; // colors.lightBlueA700
+ padding: 30px;
+ }
+}
+
+#wiki {
+ p, blockquote, ul, ol, dl, li, table, pre {
+ margin: 15px 0;
+ }
+
+ ol, ul {
+ padding-bottom: 20px;
+ }
+
+ table {
+ padding: 0;
+ border-collapse: collapse;
+ }
+ table tr {
+ border-top: 1px solid #cccccc;
+ background-color: white;
+ margin: 0;
+ padding: 0;
+ }
+ table tr:nth-child(2n) {
+ background-color: #f8f8f8;
+ }
+ table tr th {
+ font-weight: bold;
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+ }
+ table tr td {
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+ }
+ table tr th :first-child, table tr td :first-child {
+ margin-top: 0;
+ }
+ table tr th :last-child, table tr td :last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/packages/website/package.json b/packages/website/package.json
new file mode 100644
index 000000000..ba71ee0eb
--- /dev/null
+++ b/packages/website/package.json
@@ -0,0 +1,105 @@
+{
+ "name": "website",
+ "version": "0.0.0",
+ "description": "Website and 0x portal dapp",
+ "scripts": {
+ "build": "NODE_ENV=production webpack",
+ "clean": "shx rm -f public/bundle*",
+ "dev": "webpack-dev-server --content-base public --https",
+ "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;",
+ "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers",
+ "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers"
+ },
+ "config": {
+ "artifacts": "Mintable"
+ },
+ "author": "Fabio Berger",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "accounting": "^0.4.1",
+ "basscss": "^8.0.3",
+ "bignumber.js": "~4.1.0",
+ "blockies": "^0.0.2",
+ "compare-versions": "^3.0.1",
+ "dateformat": "^2.0.0",
+ "deep-equal": "^1.0.1",
+ "dharma-loan-frame": "^0.0.12",
+ "es6-promisify": "^5.0.0",
+ "ethereum-address": "^0.0.4",
+ "ethereumjs-tx": "^1.3.3",
+ "ethereumjs-util": "^5.1.1",
+ "find-versions": "^2.0.0",
+ "is-mobile": "^0.2.2",
+ "jsonschema": "^1.1.1",
+ "ledgerco": "0xProject/ledger-node-js-api",
+ "less": "^2.7.2",
+ "lodash": "^4.17.4",
+ "material-ui": "^0.17.1",
+ "moment": "^2.18.1",
+ "query-string": "^5.0.0",
+ "react": "15.6.1",
+ "react-copy-to-clipboard": "^4.2.3",
+ "react-document-title": "^2.0.3",
+ "react-dom": "15.6.1",
+ "react-highlight": "^0.10.0",
+ "react-html5video": "^2.1.0",
+ "react-inlinesvg": "^0.5.5",
+ "react-markdown": "^2.5.0",
+ "react-recaptcha": "^2.3.2",
+ "react-redux": "^5.0.3",
+ "react-router-dom": "^4.1.1",
+ "react-router-hash-link": "^1.1.0",
+ "react-scroll": "^1.5.2",
+ "react-tap-event-plugin": "^2.0.1",
+ "react-tooltip": "^3.2.7",
+ "react-waypoint": "^7.0.4",
+ "redux": "^3.6.0",
+ "scroll-to-element": "^2.0.0",
+ "semver-sort": "0.0.4",
+ "thenby": "^1.2.3",
+ "truffle-contract": "2.0.1",
+ "tslint-config-0xproject": "^0.0.2",
+ "typescript": "^2.4.1",
+ "web3": "^0.20.0",
+ "web3-provider-engine": "^11.0.0",
+ "whatwg-fetch": "^2.0.3",
+ "xml-js": "^1.3.2"
+ },
+ "devDependencies": {
+ "@types/accounting": "^0.4.1",
+ "@types/dateformat": "^1.0.1",
+ "@types/deep-equal": "^1.0.0",
+ "@types/es6-promise": "0.0.32",
+ "@types/jsonschema": "^1.1.1",
+ "@types/lodash": "^4.14.55",
+ "@types/material-ui": "0.18.0",
+ "@types/moment": "^2.13.0",
+ "@types/node": "^7.0.8",
+ "@types/query-string": "^5.0.0",
+ "@types/react": "^15.0.15",
+ "@types/react-copy-to-clipboard": "^4.2.0",
+ "@types/react-dom": "^0.14.23",
+ "@types/react-redux": "^4.4.37",
+ "@types/react-router-dom": "^4.0.4",
+ "@types/react-scroll": "0.0.31",
+ "@types/react-tap-event-plugin": "0.0.30",
+ "@types/redux": "^3.6.0",
+ "awesome-typescript-loader": "^3.1.3",
+ "copy-webpack-plugin": "^4.0.1",
+ "copyfiles": "^1.2.0",
+ "css-loader": "0.23.x",
+ "exports-loader": "0.6.x",
+ "imports-loader": "0.6.x",
+ "json-loader": "^0.5.4",
+ "less-loader": "^2.2.3",
+ "raw-loader": "^0.5.1",
+ "shx": "^0.2.2",
+ "source-map-loader": "^0.1.6",
+ "style-loader": "0.13.x",
+ "tslint": "5.8.0",
+ "web3-typescript-typings": "^0.7.1",
+ "webpack": "3.1.0",
+ "webpack-dev-middleware": "^1.10.0",
+ "webpack-dev-server": "^2.5.0"
+ }
+}
diff --git a/packages/website/public/css/atom-one-light.css b/packages/website/public/css/atom-one-light.css
new file mode 100644
index 000000000..d5bd1d2a9
--- /dev/null
+++ b/packages/website/public/css/atom-one-light.css
@@ -0,0 +1,96 @@
+/*
+
+Atom One Light by Daniel Gamage
+Original One Light Syntax theme from https://github.com/atom/one-light-syntax
+
+base: #fafafa
+mono-1: #383a42
+mono-2: #686b77
+mono-3: #a0a1a7
+hue-1: #0184bb
+hue-2: #4078f2
+hue-3: #a626a4
+hue-4: #50a14f
+hue-5: #e45649
+hue-5-2: #c91243
+hue-6: #986801
+hue-6-2: #c18401
+
+*/
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 0.5em;
+ color: #383a42;
+ background: #fafafa;
+}
+
+.hljs-comment,
+.hljs-quote {
+ color: #a0a1a7;
+ font-style: italic;
+}
+
+.hljs-doctag,
+.hljs-keyword,
+.hljs-formula {
+ color: #a626a4;
+}
+
+.hljs-section,
+.hljs-name,
+.hljs-selector-tag,
+.hljs-deletion,
+.hljs-subst {
+ color: #e45649;
+}
+
+.hljs-literal {
+ color: #0184bb;
+}
+
+.hljs-string,
+.hljs-regexp,
+.hljs-addition,
+.hljs-attribute,
+.hljs-meta-string {
+ color: #50a14f;
+}
+
+.hljs-built_in,
+.hljs-class .hljs-title {
+ color: #c18401;
+}
+
+.hljs-attr,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-type,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo,
+.hljs-number {
+ color: #986801;
+}
+
+.hljs-symbol,
+.hljs-bullet,
+.hljs-link,
+.hljs-meta,
+.hljs-selector-id,
+.hljs-title {
+ color: #4078f2;
+}
+
+.hljs-emphasis {
+ font-style: italic;
+}
+
+.hljs-strong {
+ font-weight: bold;
+}
+
+.hljs-link {
+ text-decoration: underline;
+}
diff --git a/packages/website/public/css/basscss_responsive_custom.css b/packages/website/public/css/basscss_responsive_custom.css
new file mode 100644
index 000000000..c2e802125
--- /dev/null
+++ b/packages/website/public/css/basscss_responsive_custom.css
@@ -0,0 +1,58 @@
+/* Custom Basscss Responsive Utilities */
+
+@media (max-width: 52em) {
+ .sm-center {
+ text-align: center;
+ }
+ .sm-left-align {
+ text-align: left;
+ }
+ .sm-right-align {
+ text-align: right;
+ }
+ .sm-mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .sm-right {
+ float: right;
+ }
+}
+
+@media (min-width: 52em) {
+ .md-center {
+ text-align: center;
+ }
+ .md-left-align {
+ text-align: left;
+ }
+ .md-right-align {
+ text-align: right;
+ }
+ .md-mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .md-right {
+ float: right;
+ }
+}
+
+@media (min-width: 64em) {
+ .lg-center {
+ text-align: center;
+ }
+ .lg-left-align {
+ text-align: left;
+ }
+ .lg-right-align {
+ text-align: right;
+ }
+ .lg-mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .lg-right {
+ float: right;
+ }
+}
diff --git a/packages/website/public/css/basscss_responsive_margin.css b/packages/website/public/css/basscss_responsive_margin.css
new file mode 100644
index 000000000..b601bd491
--- /dev/null
+++ b/packages/website/public/css/basscss_responsive_margin.css
@@ -0,0 +1,160 @@
+/* Basscss Responsive Margin */
+
+@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */
+
+ .sm-m0 { margin: 0 }
+ .sm-mt0 { margin-top: 0 }
+ .sm-mr0 { margin-right: 0 }
+ .sm-mb0 { margin-bottom: 0 }
+ .sm-ml0 { margin-left: 0 }
+ .sm-mx0 { margin-left: 0; margin-right: 0 }
+ .sm-my0 { margin-top: 0; margin-bottom: 0 }
+
+ .sm-m1 { margin: .5rem }
+ .sm-mt1 { margin-top: .5rem }
+ .sm-mr1 { margin-right: .5rem }
+ .sm-mb1 { margin-bottom: .5rem }
+ .sm-ml1 { margin-left: .5rem }
+ .sm-mx1 { margin-left: .5rem; margin-right: .5rem }
+ .sm-my1 { margin-top: .5rem; margin-bottom: .5rem }
+
+ .sm-m2 { margin: 1rem }
+ .sm-mt2 { margin-top: 1rem }
+ .sm-mr2 { margin-right: 1rem }
+ .sm-mb2 { margin-bottom: 1rem }
+ .sm-ml2 { margin-left: 1rem }
+ .sm-mx2 { margin-left: 1rem; margin-right: 1rem }
+ .sm-my2 { margin-top: 1rem; margin-bottom: 1rem }
+
+ .sm-m3 { margin: 2rem }
+ .sm-mt3 { margin-top: 2rem }
+ .sm-mr3 { margin-right: 2rem }
+ .sm-mb3 { margin-bottom: 2rem }
+ .sm-ml3 { margin-left: 2rem }
+ .sm-mx3 { margin-left: 2rem; margin-right: 2rem }
+ .sm-my3 { margin-top: 2rem; margin-bottom: 2rem }
+
+ .sm-m4 { margin: 4rem }
+ .sm-mt4 { margin-top: 4rem }
+ .sm-mr4 { margin-right: 4rem }
+ .sm-mb4 { margin-bottom: 4rem }
+ .sm-ml4 { margin-left: 4rem }
+ .sm-mx4 { margin-left: 4rem; margin-right: 4rem }
+ .sm-my4 { margin-top: 4rem; margin-bottom: 4rem }
+
+ .sm-mxn1 { margin-left: -.5rem; margin-right: -.5rem }
+ .sm-mxn2 { margin-left: -1rem; margin-right: -1rem }
+ .sm-mxn3 { margin-left: -2rem; margin-right: -2rem }
+ .sm-mxn4 { margin-left: -4rem; margin-right: -4rem }
+
+ .sm-ml-auto { margin-left: auto }
+ .sm-mr-auto { margin-right: auto }
+ .sm-mx-auto { margin-left: auto; margin-right: auto }
+
+}
+
+@media (min-width: 52em) {
+
+ .md-m0 { margin: 0 }
+ .md-mt0 { margin-top: 0 }
+ .md-mr0 { margin-right: 0 }
+ .md-mb0 { margin-bottom: 0 }
+ .md-ml0 { margin-left: 0 }
+ .md-mx0 { margin-left: 0; margin-right: 0 }
+ .md-my0 { margin-top: 0; margin-bottom: 0 }
+
+ .md-m1 { margin: .5rem }
+ .md-mt1 { margin-top: .5rem }
+ .md-mr1 { margin-right: .5rem }
+ .md-mb1 { margin-bottom: .5rem }
+ .md-ml1 { margin-left: .5rem }
+ .md-mx1 { margin-left: .5rem; margin-right: .5rem }
+ .md-my1 { margin-top: .5rem; margin-bottom: .5rem }
+
+ .md-m2 { margin: 1rem }
+ .md-mt2 { margin-top: 1rem }
+ .md-mr2 { margin-right: 1rem }
+ .md-mb2 { margin-bottom: 1rem }
+ .md-ml2 { margin-left: 1rem }
+ .md-mx2 { margin-left: 1rem; margin-right: 1rem }
+ .md-my2 { margin-top: 1rem; margin-bottom: 1rem }
+
+ .md-m3 { margin: 2rem }
+ .md-mt3 { margin-top: 2rem }
+ .md-mr3 { margin-right: 2rem }
+ .md-mb3 { margin-bottom: 2rem }
+ .md-ml3 { margin-left: 2rem }
+ .md-mx3 { margin-left: 2rem; margin-right: 2rem }
+ .md-my3 { margin-top: 2rem; margin-bottom: 2rem }
+
+ .md-m4 { margin: 4rem }
+ .md-mt4 { margin-top: 4rem }
+ .md-mr4 { margin-right: 4rem }
+ .md-mb4 { margin-bottom: 4rem }
+ .md-ml4 { margin-left: 4rem }
+ .md-mx4 { margin-left: 4rem; margin-right: 4rem }
+ .md-my4 { margin-top: 4rem; margin-bottom: 4rem }
+
+ .md-mxn1 { margin-left: -.5rem; margin-right: -.5rem; }
+ .md-mxn2 { margin-left: -1rem; margin-right: -1rem; }
+ .md-mxn3 { margin-left: -2rem; margin-right: -2rem; }
+ .md-mxn4 { margin-left: -4rem; margin-right: -4rem; }
+
+ .md-ml-auto { margin-left: auto }
+ .md-mr-auto { margin-right: auto }
+ .md-mx-auto { margin-left: auto; margin-right: auto; }
+
+}
+
+@media (min-width: 64em) {
+
+ .lg-m0 { margin: 0 }
+ .lg-mt0 { margin-top: 0 }
+ .lg-mr0 { margin-right: 0 }
+ .lg-mb0 { margin-bottom: 0 }
+ .lg-ml0 { margin-left: 0 }
+ .lg-mx0 { margin-left: 0; margin-right: 0 }
+ .lg-my0 { margin-top: 0; margin-bottom: 0 }
+
+ .lg-m1 { margin: .5rem }
+ .lg-mt1 { margin-top: .5rem }
+ .lg-mr1 { margin-right: .5rem }
+ .lg-mb1 { margin-bottom: .5rem }
+ .lg-ml1 { margin-left: .5rem }
+ .lg-mx1 { margin-left: .5rem; margin-right: .5rem }
+ .lg-my1 { margin-top: .5rem; margin-bottom: .5rem }
+
+ .lg-m2 { margin: 1rem }
+ .lg-mt2 { margin-top: 1rem }
+ .lg-mr2 { margin-right: 1rem }
+ .lg-mb2 { margin-bottom: 1rem }
+ .lg-ml2 { margin-left: 1rem }
+ .lg-mx2 { margin-left: 1rem; margin-right: 1rem }
+ .lg-my2 { margin-top: 1rem; margin-bottom: 1rem }
+
+ .lg-m3 { margin: 2rem }
+ .lg-mt3 { margin-top: 2rem }
+ .lg-mr3 { margin-right: 2rem }
+ .lg-mb3 { margin-bottom: 2rem }
+ .lg-ml3 { margin-left: 2rem }
+ .lg-mx3 { margin-left: 2rem; margin-right: 2rem }
+ .lg-my3 { margin-top: 2rem; margin-bottom: 2rem }
+
+ .lg-m4 { margin: 4rem }
+ .lg-mt4 { margin-top: 4rem }
+ .lg-mr4 { margin-right: 4rem }
+ .lg-mb4 { margin-bottom: 4rem }
+ .lg-ml4 { margin-left: 4rem }
+ .lg-mx4 { margin-left: 4rem; margin-right: 4rem }
+ .lg-my4 { margin-top: 4rem; margin-bottom: 4rem }
+
+ .lg-mxn1 { margin-left: -.5rem; margin-right: -.5rem; }
+ .lg-mxn2 { margin-left: -1rem; margin-right: -1rem; }
+ .lg-mxn3 { margin-left: -2rem; margin-right: -2rem; }
+ .lg-mxn4 { margin-left: -4rem; margin-right: -4rem; }
+
+ .lg-ml-auto { margin-left: auto }
+ .lg-mr-auto { margin-right: auto }
+ .lg-mx-auto { margin-left: auto; margin-right: auto; }
+
+}
diff --git a/packages/website/public/css/basscss_responsive_padding.css b/packages/website/public/css/basscss_responsive_padding.css
new file mode 100644
index 000000000..e027c2d65
--- /dev/null
+++ b/packages/website/public/css/basscss_responsive_padding.css
@@ -0,0 +1,134 @@
+/* Basscss Responsive Padding */
+/* Modified by Fabio Berger to include xs prefix */
+
+@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */
+
+ .sm-p0 { padding: 0 }
+ .sm-pt0 { padding-top: 0 }
+ .sm-pr0 { padding-right: 0 }
+ .sm-pb0 { padding-bottom: 0 }
+ .sm-pl0 { padding-left: 0 }
+ .sm-px0 { padding-left: 0; padding-right: 0 }
+ .sm-py0 { padding-top: 0; padding-bottom: 0 }
+
+ .sm-p1 { padding: .5rem }
+ .sm-pt1 { padding-top: .5rem }
+ .sm-pr1 { padding-right: .5rem }
+ .sm-pb1 { padding-bottom: .5rem }
+ .sm-pl1 { padding-left: .5rem }
+ .sm-px1 { padding-left: .5rem; padding-right: .5rem }
+ .sm-py1 { padding-top: .5rem; padding-bottom: .5rem }
+
+ .sm-p2 { padding: 1rem }
+ .sm-pt2 { padding-top: 1rem }
+ .sm-pr2 { padding-right: 1rem }
+ .sm-pb2 { padding-bottom: 1rem }
+ .sm-pl2 { padding-left: 1rem }
+ .sm-px2 { padding-left: 1rem; padding-right: 1rem }
+ .sm-py2 { padding-top: 1rem; padding-bottom: 1rem }
+
+ .sm-p3 { padding: 2rem }
+ .sm-pt3 { padding-top: 2rem }
+ .sm-pr3 { padding-right: 2rem }
+ .sm-pb3 { padding-bottom: 2rem }
+ .sm-pl3 { padding-left: 2rem }
+ .sm-px3 { padding-left: 2rem; padding-right: 2rem }
+ .sm-py3 { padding-top: 2rem; padding-bottom: 2rem }
+
+ .sm-p4 { padding: 4rem }
+ .sm-pt4 { padding-top: 4rem }
+ .sm-pr4 { padding-right: 4rem }
+ .sm-pb4 { padding-bottom: 4rem }
+ .sm-pl4 { padding-left: 4rem }
+ .sm-px4 { padding-left: 4rem; padding-right: 4rem }
+ .sm-py4 { padding-top: 4rem; padding-bottom: 4rem }
+
+}
+
+@media (min-width: 52em) {
+
+ .md-p0 { padding: 0 }
+ .md-pt0 { padding-top: 0 }
+ .md-pr0 { padding-right: 0 }
+ .md-pb0 { padding-bottom: 0 }
+ .md-pl0 { padding-left: 0 }
+ .md-px0 { padding-left: 0; padding-right: 0 }
+ .md-py0 { padding-top: 0; padding-bottom: 0 }
+
+ .md-p1 { padding: .5rem }
+ .md-pt1 { padding-top: .5rem }
+ .md-pr1 { padding-right: .5rem }
+ .md-pb1 { padding-bottom: .5rem }
+ .md-pl1 { padding-left: .5rem }
+ .md-px1 { padding-left: .5rem; padding-right: .5rem }
+ .md-py1 { padding-top: .5rem; padding-bottom: .5rem }
+
+ .md-p2 { padding: 1rem }
+ .md-pt2 { padding-top: 1rem }
+ .md-pr2 { padding-right: 1rem }
+ .md-pb2 { padding-bottom: 1rem }
+ .md-pl2 { padding-left: 1rem }
+ .md-px2 { padding-left: 1rem; padding-right: 1rem }
+ .md-py2 { padding-top: 1rem; padding-bottom: 1rem }
+
+ .md-p3 { padding: 2rem }
+ .md-pt3 { padding-top: 2rem }
+ .md-pr3 { padding-right: 2rem }
+ .md-pb3 { padding-bottom: 2rem }
+ .md-pl3 { padding-left: 2rem }
+ .md-px3 { padding-left: 2rem; padding-right: 2rem }
+ .md-py3 { padding-top: 2rem; padding-bottom: 2rem }
+
+ .md-p4 { padding: 4rem }
+ .md-pt4 { padding-top: 4rem }
+ .md-pr4 { padding-right: 4rem }
+ .md-pb4 { padding-bottom: 4rem }
+ .md-pl4 { padding-left: 4rem }
+ .md-px4 { padding-left: 4rem; padding-right: 4rem }
+ .md-py4 { padding-top: 4rem; padding-bottom: 4rem }
+
+}
+
+@media (min-width: 64em) {
+
+ .lg-p0 { padding: 0 }
+ .lg-pt0 { padding-top: 0 }
+ .lg-pr0 { padding-right: 0 }
+ .lg-pb0 { padding-bottom: 0 }
+ .lg-pl0 { padding-left: 0 }
+ .lg-px0 { padding-left: 0; padding-right: 0 }
+ .lg-py0 { padding-top: 0; padding-bottom: 0 }
+
+ .lg-p1 { padding: .5rem }
+ .lg-pt1 { padding-top: .5rem }
+ .lg-pr1 { padding-right: .5rem }
+ .lg-pb1 { padding-bottom: .5rem }
+ .lg-pl1 { padding-left: .5rem }
+ .lg-px1 { padding-left: .5rem; padding-right: .5rem }
+ .lg-py1 { padding-top: .5rem; padding-bottom: .5rem }
+
+ .lg-p2 { padding: 1rem }
+ .lg-pt2 { padding-top: 1rem }
+ .lg-pr2 { padding-right: 1rem }
+ .lg-pb2 { padding-bottom: 1rem }
+ .lg-pl2 { padding-left: 1rem }
+ .lg-px2 { padding-left: 1rem; padding-right: 1rem }
+ .lg-py2 { padding-top: 1rem; padding-bottom: 1rem }
+
+ .lg-p3 { padding: 2rem }
+ .lg-pt3 { padding-top: 2rem }
+ .lg-pr3 { padding-right: 2rem }
+ .lg-pb3 { padding-bottom: 2rem }
+ .lg-pl3 { padding-left: 2rem }
+ .lg-px3 { padding-left: 2rem; padding-right: 2rem }
+ .lg-py3 { padding-top: 2rem; padding-bottom: 2rem }
+
+ .lg-p4 { padding: 4rem }
+ .lg-pt4 { padding-top: 4rem }
+ .lg-pr4 { padding-right: 4rem }
+ .lg-pb4 { padding-bottom: 4rem }
+ .lg-pl4 { padding-left: 4rem }
+ .lg-px4 { padding-left: 4rem; padding-right: 4rem }
+ .lg-py4 { padding-top: 4rem; padding-bottom: 4rem }
+
+}
diff --git a/packages/website/public/css/basscss_responsive_type_scale.css b/packages/website/public/css/basscss_responsive_type_scale.css
new file mode 100644
index 000000000..cae23b4e7
--- /dev/null
+++ b/packages/website/public/css/basscss_responsive_type_scale.css
@@ -0,0 +1,35 @@
+/* Basscss Responsive Type Scale */
+/* Modified by Fabio Berger to include xs prefix */
+
+@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */
+ .sm-h00 { font-size: 4rem }
+ .sm-h0 { font-size: 3rem }
+ .sm-h1 { font-size: 2rem }
+ .sm-h2 { font-size: 1.5rem }
+ .sm-h3 { font-size: 1.25rem }
+ .sm-h4 { font-size: 1rem }
+ .sm-h5 { font-size: .875rem }
+ .sm-h6 { font-size: .75rem }
+}
+
+@media (min-width: 52em) {
+ .md-h00 { font-size: 4rem }
+ .md-h0 { font-size: 3rem }
+ .md-h1 { font-size: 2rem }
+ .md-h2 { font-size: 1.5rem }
+ .md-h3 { font-size: 1.25rem }
+ .md-h4 { font-size: 1rem }
+ .md-h5 { font-size: .875rem }
+ .md-h6 { font-size: .75rem }
+}
+
+@media (min-width: 64em) {
+ .lg-h00 { font-size: 4rem }
+ .lg-h0 { font-size: 3rem }
+ .lg-h1 { font-size: 2rem }
+ .lg-h2 { font-size: 1.5rem }
+ .lg-h3 { font-size: 1.25rem }
+ .lg-h4 { font-size: 1rem }
+ .lg-h5 { font-size: .875rem }
+ .lg-h6 { font-size: .75rem }
+}
diff --git a/packages/website/public/css/material-design-iconic-font.css b/packages/website/public/css/material-design-iconic-font.css
new file mode 100755
index 000000000..81d090a8b
--- /dev/null
+++ b/packages/website/public/css/material-design-iconic-font.css
@@ -0,0 +1,5166 @@
+/*!
+ * Material Design Iconic Font by Sergey Kupletsky (@zavoloklom) - http://zavoloklom.github.io/material-design-iconic-font/
+ * License - http://zavoloklom.github.io/material-design-iconic-font/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+@font-face {
+ font-family: 'Material-Design-Iconic-Font';
+ src: url('../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0') format('woff2'), url('../fonts/Material-Design-Iconic-Font.woff?v=2.2.0') format('woff'), url('../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+.zmdi {
+ display: inline-block;
+ font: normal normal normal 14px/1 'Material-Design-Iconic-Font';
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.zmdi-hc-lg {
+ font-size: 1.33333333em;
+ line-height: 0.75em;
+ vertical-align: -15%;
+}
+.zmdi-hc-2x {
+ font-size: 2em;
+}
+.zmdi-hc-3x {
+ font-size: 3em;
+}
+.zmdi-hc-4x {
+ font-size: 4em;
+}
+.zmdi-hc-5x {
+ font-size: 5em;
+}
+.zmdi-hc-fw {
+ width: 1.28571429em;
+ text-align: center;
+}
+.zmdi-hc-ul {
+ padding-left: 0;
+ margin-left: 2.14285714em;
+ list-style-type: none;
+}
+.zmdi-hc-ul > li {
+ position: relative;
+}
+.zmdi-hc-li {
+ position: absolute;
+ left: -2.14285714em;
+ width: 2.14285714em;
+ top: 0.14285714em;
+ text-align: center;
+}
+.zmdi-hc-li.zmdi-hc-lg {
+ left: -1.85714286em;
+}
+.zmdi-hc-border {
+ padding: .1em .25em;
+ border: solid 0.1em #9e9e9e;
+ border-radius: 2px;
+}
+.zmdi-hc-border-circle {
+ padding: .1em .25em;
+ border: solid 0.1em #9e9e9e;
+ border-radius: 50%;
+}
+.zmdi.pull-left {
+ float: left;
+ margin-right: .15em;
+}
+.zmdi.pull-right {
+ float: right;
+ margin-left: .15em;
+}
+.zmdi-hc-spin {
+ -webkit-animation: zmdi-spin 1.5s infinite linear;
+ animation: zmdi-spin 1.5s infinite linear;
+}
+.zmdi-hc-spin-reverse {
+ -webkit-animation: zmdi-spin-reverse 1.5s infinite linear;
+ animation: zmdi-spin-reverse 1.5s infinite linear;
+}
+@-webkit-keyframes zmdi-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@keyframes zmdi-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-webkit-keyframes zmdi-spin-reverse {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(-359deg);
+ transform: rotate(-359deg);
+ }
+}
+@keyframes zmdi-spin-reverse {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(-359deg);
+ transform: rotate(-359deg);
+ }
+}
+.zmdi-hc-rotate-90 {
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+.zmdi-hc-rotate-180 {
+ -webkit-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+.zmdi-hc-rotate-270 {
+ -webkit-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ transform: rotate(270deg);
+}
+.zmdi-hc-flip-horizontal {
+ -webkit-transform: scale(-1, 1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(-1, 1);
+}
+.zmdi-hc-flip-vertical {
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1);
+}
+.zmdi-hc-stack {
+ position: relative;
+ display: inline-block;
+ width: 2em;
+ height: 2em;
+ line-height: 2em;
+ vertical-align: middle;
+}
+.zmdi-hc-stack-1x,
+.zmdi-hc-stack-2x {
+ position: absolute;
+ left: 0;
+ width: 100%;
+ text-align: center;
+}
+.zmdi-hc-stack-1x {
+ line-height: inherit;
+}
+.zmdi-hc-stack-2x {
+ font-size: 2em;
+}
+.zmdi-hc-inverse {
+ color: #ffffff;
+}
+/* Material Design Iconic Font uses the Unicode Private Use Area (PUA) to ensure screen
+ readers do not read off random characters that represent icons */
+.zmdi-3d-rotation:before {
+ content: '\f101';
+}
+.zmdi-airplane-off:before {
+ content: '\f102';
+}
+.zmdi-airplane:before {
+ content: '\f103';
+}
+.zmdi-album:before {
+ content: '\f104';
+}
+.zmdi-archive:before {
+ content: '\f105';
+}
+.zmdi-assignment-account:before {
+ content: '\f106';
+}
+.zmdi-assignment-alert:before {
+ content: '\f107';
+}
+.zmdi-assignment-check:before {
+ content: '\f108';
+}
+.zmdi-assignment-o:before {
+ content: '\f109';
+}
+.zmdi-assignment-return:before {
+ content: '\f10a';
+}
+.zmdi-assignment-returned:before {
+ content: '\f10b';
+}
+.zmdi-assignment:before {
+ content: '\f10c';
+}
+.zmdi-attachment-alt:before {
+ content: '\f10d';
+}
+.zmdi-attachment:before {
+ content: '\f10e';
+}
+.zmdi-audio:before {
+ content: '\f10f';
+}
+.zmdi-badge-check:before {
+ content: '\f110';
+}
+.zmdi-balance-wallet:before {
+ content: '\f111';
+}
+.zmdi-balance:before {
+ content: '\f112';
+}
+.zmdi-battery-alert:before {
+ content: '\f113';
+}
+.zmdi-battery-flash:before {
+ content: '\f114';
+}
+.zmdi-battery-unknown:before {
+ content: '\f115';
+}
+.zmdi-battery:before {
+ content: '\f116';
+}
+.zmdi-bike:before {
+ content: '\f117';
+}
+.zmdi-block-alt:before {
+ content: '\f118';
+}
+.zmdi-block:before {
+ content: '\f119';
+}
+.zmdi-boat:before {
+ content: '\f11a';
+}
+.zmdi-book-image:before {
+ content: '\f11b';
+}
+.zmdi-book:before {
+ content: '\f11c';
+}
+.zmdi-bookmark-outline:before {
+ content: '\f11d';
+}
+.zmdi-bookmark:before {
+ content: '\f11e';
+}
+.zmdi-brush:before {
+ content: '\f11f';
+}
+.zmdi-bug:before {
+ content: '\f120';
+}
+.zmdi-bus:before {
+ content: '\f121';
+}
+.zmdi-cake:before {
+ content: '\f122';
+}
+.zmdi-car-taxi:before {
+ content: '\f123';
+}
+.zmdi-car-wash:before {
+ content: '\f124';
+}
+.zmdi-car:before {
+ content: '\f125';
+}
+.zmdi-card-giftcard:before {
+ content: '\f126';
+}
+.zmdi-card-membership:before {
+ content: '\f127';
+}
+.zmdi-card-travel:before {
+ content: '\f128';
+}
+.zmdi-card:before {
+ content: '\f129';
+}
+.zmdi-case-check:before {
+ content: '\f12a';
+}
+.zmdi-case-download:before {
+ content: '\f12b';
+}
+.zmdi-case-play:before {
+ content: '\f12c';
+}
+.zmdi-case:before {
+ content: '\f12d';
+}
+.zmdi-cast-connected:before {
+ content: '\f12e';
+}
+.zmdi-cast:before {
+ content: '\f12f';
+}
+.zmdi-chart-donut:before {
+ content: '\f130';
+}
+.zmdi-chart:before {
+ content: '\f131';
+}
+.zmdi-city-alt:before {
+ content: '\f132';
+}
+.zmdi-city:before {
+ content: '\f133';
+}
+.zmdi-close-circle-o:before {
+ content: '\f134';
+}
+.zmdi-close-circle:before {
+ content: '\f135';
+}
+.zmdi-close:before {
+ content: '\f136';
+}
+.zmdi-cocktail:before {
+ content: '\f137';
+}
+.zmdi-code-setting:before {
+ content: '\f138';
+}
+.zmdi-code-smartphone:before {
+ content: '\f139';
+}
+.zmdi-code:before {
+ content: '\f13a';
+}
+.zmdi-coffee:before {
+ content: '\f13b';
+}
+.zmdi-collection-bookmark:before {
+ content: '\f13c';
+}
+.zmdi-collection-case-play:before {
+ content: '\f13d';
+}
+.zmdi-collection-folder-image:before {
+ content: '\f13e';
+}
+.zmdi-collection-image-o:before {
+ content: '\f13f';
+}
+.zmdi-collection-image:before {
+ content: '\f140';
+}
+.zmdi-collection-item-1:before {
+ content: '\f141';
+}
+.zmdi-collection-item-2:before {
+ content: '\f142';
+}
+.zmdi-collection-item-3:before {
+ content: '\f143';
+}
+.zmdi-collection-item-4:before {
+ content: '\f144';
+}
+.zmdi-collection-item-5:before {
+ content: '\f145';
+}
+.zmdi-collection-item-6:before {
+ content: '\f146';
+}
+.zmdi-collection-item-7:before {
+ content: '\f147';
+}
+.zmdi-collection-item-8:before {
+ content: '\f148';
+}
+.zmdi-collection-item-9-plus:before {
+ content: '\f149';
+}
+.zmdi-collection-item-9:before {
+ content: '\f14a';
+}
+.zmdi-collection-item:before {
+ content: '\f14b';
+}
+.zmdi-collection-music:before {
+ content: '\f14c';
+}
+.zmdi-collection-pdf:before {
+ content: '\f14d';
+}
+.zmdi-collection-plus:before {
+ content: '\f14e';
+}
+.zmdi-collection-speaker:before {
+ content: '\f14f';
+}
+.zmdi-collection-text:before {
+ content: '\f150';
+}
+.zmdi-collection-video:before {
+ content: '\f151';
+}
+.zmdi-compass:before {
+ content: '\f152';
+}
+.zmdi-cutlery:before {
+ content: '\f153';
+}
+.zmdi-delete:before {
+ content: '\f154';
+}
+.zmdi-dialpad:before {
+ content: '\f155';
+}
+.zmdi-dns:before {
+ content: '\f156';
+}
+.zmdi-drink:before {
+ content: '\f157';
+}
+.zmdi-edit:before {
+ content: '\f158';
+}
+.zmdi-email-open:before {
+ content: '\f159';
+}
+.zmdi-email:before {
+ content: '\f15a';
+}
+.zmdi-eye-off:before {
+ content: '\f15b';
+}
+.zmdi-eye:before {
+ content: '\f15c';
+}
+.zmdi-eyedropper:before {
+ content: '\f15d';
+}
+.zmdi-favorite-outline:before {
+ content: '\f15e';
+}
+.zmdi-favorite:before {
+ content: '\f15f';
+}
+.zmdi-filter-list:before {
+ content: '\f160';
+}
+.zmdi-fire:before {
+ content: '\f161';
+}
+.zmdi-flag:before {
+ content: '\f162';
+}
+.zmdi-flare:before {
+ content: '\f163';
+}
+.zmdi-flash-auto:before {
+ content: '\f164';
+}
+.zmdi-flash-off:before {
+ content: '\f165';
+}
+.zmdi-flash:before {
+ content: '\f166';
+}
+.zmdi-flip:before {
+ content: '\f167';
+}
+.zmdi-flower-alt:before {
+ content: '\f168';
+}
+.zmdi-flower:before {
+ content: '\f169';
+}
+.zmdi-font:before {
+ content: '\f16a';
+}
+.zmdi-fullscreen-alt:before {
+ content: '\f16b';
+}
+.zmdi-fullscreen-exit:before {
+ content: '\f16c';
+}
+.zmdi-fullscreen:before {
+ content: '\f16d';
+}
+.zmdi-functions:before {
+ content: '\f16e';
+}
+.zmdi-gas-station:before {
+ content: '\f16f';
+}
+.zmdi-gesture:before {
+ content: '\f170';
+}
+.zmdi-globe-alt:before {
+ content: '\f171';
+}
+.zmdi-globe-lock:before {
+ content: '\f172';
+}
+.zmdi-globe:before {
+ content: '\f173';
+}
+.zmdi-graduation-cap:before {
+ content: '\f174';
+}
+.zmdi-home:before {
+ content: '\f175';
+}
+.zmdi-hospital-alt:before {
+ content: '\f176';
+}
+.zmdi-hospital:before {
+ content: '\f177';
+}
+.zmdi-hotel:before {
+ content: '\f178';
+}
+.zmdi-hourglass-alt:before {
+ content: '\f179';
+}
+.zmdi-hourglass-outline:before {
+ content: '\f17a';
+}
+.zmdi-hourglass:before {
+ content: '\f17b';
+}
+.zmdi-http:before {
+ content: '\f17c';
+}
+.zmdi-image-alt:before {
+ content: '\f17d';
+}
+.zmdi-image-o:before {
+ content: '\f17e';
+}
+.zmdi-image:before {
+ content: '\f17f';
+}
+.zmdi-inbox:before {
+ content: '\f180';
+}
+.zmdi-invert-colors-off:before {
+ content: '\f181';
+}
+.zmdi-invert-colors:before {
+ content: '\f182';
+}
+.zmdi-key:before {
+ content: '\f183';
+}
+.zmdi-label-alt-outline:before {
+ content: '\f184';
+}
+.zmdi-label-alt:before {
+ content: '\f185';
+}
+.zmdi-label-heart:before {
+ content: '\f186';
+}
+.zmdi-label:before {
+ content: '\f187';
+}
+.zmdi-labels:before {
+ content: '\f188';
+}
+.zmdi-lamp:before {
+ content: '\f189';
+}
+.zmdi-landscape:before {
+ content: '\f18a';
+}
+.zmdi-layers-off:before {
+ content: '\f18b';
+}
+.zmdi-layers:before {
+ content: '\f18c';
+}
+.zmdi-library:before {
+ content: '\f18d';
+}
+.zmdi-link:before {
+ content: '\f18e';
+}
+.zmdi-lock-open:before {
+ content: '\f18f';
+}
+.zmdi-lock-outline:before {
+ content: '\f190';
+}
+.zmdi-lock:before {
+ content: '\f191';
+}
+.zmdi-mail-reply-all:before {
+ content: '\f192';
+}
+.zmdi-mail-reply:before {
+ content: '\f193';
+}
+.zmdi-mail-send:before {
+ content: '\f194';
+}
+.zmdi-mall:before {
+ content: '\f195';
+}
+.zmdi-map:before {
+ content: '\f196';
+}
+.zmdi-menu:before {
+ content: '\f197';
+}
+.zmdi-money-box:before {
+ content: '\f198';
+}
+.zmdi-money-off:before {
+ content: '\f199';
+}
+.zmdi-money:before {
+ content: '\f19a';
+}
+.zmdi-more-vert:before {
+ content: '\f19b';
+}
+.zmdi-more:before {
+ content: '\f19c';
+}
+.zmdi-movie-alt:before {
+ content: '\f19d';
+}
+.zmdi-movie:before {
+ content: '\f19e';
+}
+.zmdi-nature-people:before {
+ content: '\f19f';
+}
+.zmdi-nature:before {
+ content: '\f1a0';
+}
+.zmdi-navigation:before {
+ content: '\f1a1';
+}
+.zmdi-open-in-browser:before {
+ content: '\f1a2';
+}
+.zmdi-open-in-new:before {
+ content: '\f1a3';
+}
+.zmdi-palette:before {
+ content: '\f1a4';
+}
+.zmdi-parking:before {
+ content: '\f1a5';
+}
+.zmdi-pin-account:before {
+ content: '\f1a6';
+}
+.zmdi-pin-assistant:before {
+ content: '\f1a7';
+}
+.zmdi-pin-drop:before {
+ content: '\f1a8';
+}
+.zmdi-pin-help:before {
+ content: '\f1a9';
+}
+.zmdi-pin-off:before {
+ content: '\f1aa';
+}
+.zmdi-pin:before {
+ content: '\f1ab';
+}
+.zmdi-pizza:before {
+ content: '\f1ac';
+}
+.zmdi-plaster:before {
+ content: '\f1ad';
+}
+.zmdi-power-setting:before {
+ content: '\f1ae';
+}
+.zmdi-power:before {
+ content: '\f1af';
+}
+.zmdi-print:before {
+ content: '\f1b0';
+}
+.zmdi-puzzle-piece:before {
+ content: '\f1b1';
+}
+.zmdi-quote:before {
+ content: '\f1b2';
+}
+.zmdi-railway:before {
+ content: '\f1b3';
+}
+.zmdi-receipt:before {
+ content: '\f1b4';
+}
+.zmdi-refresh-alt:before {
+ content: '\f1b5';
+}
+.zmdi-refresh-sync-alert:before {
+ content: '\f1b6';
+}
+.zmdi-refresh-sync-off:before {
+ content: '\f1b7';
+}
+.zmdi-refresh-sync:before {
+ content: '\f1b8';
+}
+.zmdi-refresh:before {
+ content: '\f1b9';
+}
+.zmdi-roller:before {
+ content: '\f1ba';
+}
+.zmdi-ruler:before {
+ content: '\f1bb';
+}
+.zmdi-scissors:before {
+ content: '\f1bc';
+}
+.zmdi-screen-rotation-lock:before {
+ content: '\f1bd';
+}
+.zmdi-screen-rotation:before {
+ content: '\f1be';
+}
+.zmdi-search-for:before {
+ content: '\f1bf';
+}
+.zmdi-search-in-file:before {
+ content: '\f1c0';
+}
+.zmdi-search-in-page:before {
+ content: '\f1c1';
+}
+.zmdi-search-replace:before {
+ content: '\f1c2';
+}
+.zmdi-search:before {
+ content: '\f1c3';
+}
+.zmdi-seat:before {
+ content: '\f1c4';
+}
+.zmdi-settings-square:before {
+ content: '\f1c5';
+}
+.zmdi-settings:before {
+ content: '\f1c6';
+}
+.zmdi-shield-check:before {
+ content: '\f1c7';
+}
+.zmdi-shield-security:before {
+ content: '\f1c8';
+}
+.zmdi-shopping-basket:before {
+ content: '\f1c9';
+}
+.zmdi-shopping-cart-plus:before {
+ content: '\f1ca';
+}
+.zmdi-shopping-cart:before {
+ content: '\f1cb';
+}
+.zmdi-sign-in:before {
+ content: '\f1cc';
+}
+.zmdi-sort-amount-asc:before {
+ content: '\f1cd';
+}
+.zmdi-sort-amount-desc:before {
+ content: '\f1ce';
+}
+.zmdi-sort-asc:before {
+ content: '\f1cf';
+}
+.zmdi-sort-desc:before {
+ content: '\f1d0';
+}
+.zmdi-spellcheck:before {
+ content: '\f1d1';
+}
+.zmdi-storage:before {
+ content: '\f1d2';
+}
+.zmdi-store-24:before {
+ content: '\f1d3';
+}
+.zmdi-store:before {
+ content: '\f1d4';
+}
+.zmdi-subway:before {
+ content: '\f1d5';
+}
+.zmdi-sun:before {
+ content: '\f1d6';
+}
+.zmdi-tab-unselected:before {
+ content: '\f1d7';
+}
+.zmdi-tab:before {
+ content: '\f1d8';
+}
+.zmdi-tag-close:before {
+ content: '\f1d9';
+}
+.zmdi-tag-more:before {
+ content: '\f1da';
+}
+.zmdi-tag:before {
+ content: '\f1db';
+}
+.zmdi-thumb-down:before {
+ content: '\f1dc';
+}
+.zmdi-thumb-up-down:before {
+ content: '\f1dd';
+}
+.zmdi-thumb-up:before {
+ content: '\f1de';
+}
+.zmdi-ticket-star:before {
+ content: '\f1df';
+}
+.zmdi-toll:before {
+ content: '\f1e0';
+}
+.zmdi-toys:before {
+ content: '\f1e1';
+}
+.zmdi-traffic:before {
+ content: '\f1e2';
+}
+.zmdi-translate:before {
+ content: '\f1e3';
+}
+.zmdi-triangle-down:before {
+ content: '\f1e4';
+}
+.zmdi-triangle-up:before {
+ content: '\f1e5';
+}
+.zmdi-truck:before {
+ content: '\f1e6';
+}
+.zmdi-turning-sign:before {
+ content: '\f1e7';
+}
+.zmdi-wallpaper:before {
+ content: '\f1e8';
+}
+.zmdi-washing-machine:before {
+ content: '\f1e9';
+}
+.zmdi-window-maximize:before {
+ content: '\f1ea';
+}
+.zmdi-window-minimize:before {
+ content: '\f1eb';
+}
+.zmdi-window-restore:before {
+ content: '\f1ec';
+}
+.zmdi-wrench:before {
+ content: '\f1ed';
+}
+.zmdi-zoom-in:before {
+ content: '\f1ee';
+}
+.zmdi-zoom-out:before {
+ content: '\f1ef';
+}
+.zmdi-alert-circle-o:before {
+ content: '\f1f0';
+}
+.zmdi-alert-circle:before {
+ content: '\f1f1';
+}
+.zmdi-alert-octagon:before {
+ content: '\f1f2';
+}
+.zmdi-alert-polygon:before {
+ content: '\f1f3';
+}
+.zmdi-alert-triangle:before {
+ content: '\f1f4';
+}
+.zmdi-help-outline:before {
+ content: '\f1f5';
+}
+.zmdi-help:before {
+ content: '\f1f6';
+}
+.zmdi-info-outline:before {
+ content: '\f1f7';
+}
+.zmdi-info:before {
+ content: '\f1f8';
+}
+.zmdi-notifications-active:before {
+ content: '\f1f9';
+}
+.zmdi-notifications-add:before {
+ content: '\f1fa';
+}
+.zmdi-notifications-none:before {
+ content: '\f1fb';
+}
+.zmdi-notifications-off:before {
+ content: '\f1fc';
+}
+.zmdi-notifications-paused:before {
+ content: '\f1fd';
+}
+.zmdi-notifications:before {
+ content: '\f1fe';
+}
+.zmdi-account-add:before {
+ content: '\f1ff';
+}
+.zmdi-account-box-mail:before {
+ content: '\f200';
+}
+.zmdi-account-box-o:before {
+ content: '\f201';
+}
+.zmdi-account-box-phone:before {
+ content: '\f202';
+}
+.zmdi-account-box:before {
+ content: '\f203';
+}
+.zmdi-account-calendar:before {
+ content: '\f204';
+}
+.zmdi-account-circle:before {
+ content: '\f205';
+}
+.zmdi-account-o:before {
+ content: '\f206';
+}
+.zmdi-account:before {
+ content: '\f207';
+}
+.zmdi-accounts-add:before {
+ content: '\f208';
+}
+.zmdi-accounts-alt:before {
+ content: '\f209';
+}
+.zmdi-accounts-list-alt:before {
+ content: '\f20a';
+}
+.zmdi-accounts-list:before {
+ content: '\f20b';
+}
+.zmdi-accounts-outline:before {
+ content: '\f20c';
+}
+.zmdi-accounts:before {
+ content: '\f20d';
+}
+.zmdi-face:before {
+ content: '\f20e';
+}
+.zmdi-female:before {
+ content: '\f20f';
+}
+.zmdi-male-alt:before {
+ content: '\f210';
+}
+.zmdi-male-female:before {
+ content: '\f211';
+}
+.zmdi-male:before {
+ content: '\f212';
+}
+.zmdi-mood-bad:before {
+ content: '\f213';
+}
+.zmdi-mood:before {
+ content: '\f214';
+}
+.zmdi-run:before {
+ content: '\f215';
+}
+.zmdi-walk:before {
+ content: '\f216';
+}
+.zmdi-cloud-box:before {
+ content: '\f217';
+}
+.zmdi-cloud-circle:before {
+ content: '\f218';
+}
+.zmdi-cloud-done:before {
+ content: '\f219';
+}
+.zmdi-cloud-download:before {
+ content: '\f21a';
+}
+.zmdi-cloud-off:before {
+ content: '\f21b';
+}
+.zmdi-cloud-outline-alt:before {
+ content: '\f21c';
+}
+.zmdi-cloud-outline:before {
+ content: '\f21d';
+}
+.zmdi-cloud-upload:before {
+ content: '\f21e';
+}
+.zmdi-cloud:before {
+ content: '\f21f';
+}
+.zmdi-download:before {
+ content: '\f220';
+}
+.zmdi-file-plus:before {
+ content: '\f221';
+}
+.zmdi-file-text:before {
+ content: '\f222';
+}
+.zmdi-file:before {
+ content: '\f223';
+}
+.zmdi-folder-outline:before {
+ content: '\f224';
+}
+.zmdi-folder-person:before {
+ content: '\f225';
+}
+.zmdi-folder-star-alt:before {
+ content: '\f226';
+}
+.zmdi-folder-star:before {
+ content: '\f227';
+}
+.zmdi-folder:before {
+ content: '\f228';
+}
+.zmdi-gif:before {
+ content: '\f229';
+}
+.zmdi-upload:before {
+ content: '\f22a';
+}
+.zmdi-border-all:before {
+ content: '\f22b';
+}
+.zmdi-border-bottom:before {
+ content: '\f22c';
+}
+.zmdi-border-clear:before {
+ content: '\f22d';
+}
+.zmdi-border-color:before {
+ content: '\f22e';
+}
+.zmdi-border-horizontal:before {
+ content: '\f22f';
+}
+.zmdi-border-inner:before {
+ content: '\f230';
+}
+.zmdi-border-left:before {
+ content: '\f231';
+}
+.zmdi-border-outer:before {
+ content: '\f232';
+}
+.zmdi-border-right:before {
+ content: '\f233';
+}
+.zmdi-border-style:before {
+ content: '\f234';
+}
+.zmdi-border-top:before {
+ content: '\f235';
+}
+.zmdi-border-vertical:before {
+ content: '\f236';
+}
+.zmdi-copy:before {
+ content: '\f237';
+}
+.zmdi-crop:before {
+ content: '\f238';
+}
+.zmdi-format-align-center:before {
+ content: '\f239';
+}
+.zmdi-format-align-justify:before {
+ content: '\f23a';
+}
+.zmdi-format-align-left:before {
+ content: '\f23b';
+}
+.zmdi-format-align-right:before {
+ content: '\f23c';
+}
+.zmdi-format-bold:before {
+ content: '\f23d';
+}
+.zmdi-format-clear-all:before {
+ content: '\f23e';
+}
+.zmdi-format-clear:before {
+ content: '\f23f';
+}
+.zmdi-format-color-fill:before {
+ content: '\f240';
+}
+.zmdi-format-color-reset:before {
+ content: '\f241';
+}
+.zmdi-format-color-text:before {
+ content: '\f242';
+}
+.zmdi-format-indent-decrease:before {
+ content: '\f243';
+}
+.zmdi-format-indent-increase:before {
+ content: '\f244';
+}
+.zmdi-format-italic:before {
+ content: '\f245';
+}
+.zmdi-format-line-spacing:before {
+ content: '\f246';
+}
+.zmdi-format-list-bulleted:before {
+ content: '\f247';
+}
+.zmdi-format-list-numbered:before {
+ content: '\f248';
+}
+.zmdi-format-ltr:before {
+ content: '\f249';
+}
+.zmdi-format-rtl:before {
+ content: '\f24a';
+}
+.zmdi-format-size:before {
+ content: '\f24b';
+}
+.zmdi-format-strikethrough-s:before {
+ content: '\f24c';
+}
+.zmdi-format-strikethrough:before {
+ content: '\f24d';
+}
+.zmdi-format-subject:before {
+ content: '\f24e';
+}
+.zmdi-format-underlined:before {
+ content: '\f24f';
+}
+.zmdi-format-valign-bottom:before {
+ content: '\f250';
+}
+.zmdi-format-valign-center:before {
+ content: '\f251';
+}
+.zmdi-format-valign-top:before {
+ content: '\f252';
+}
+.zmdi-redo:before {
+ content: '\f253';
+}
+.zmdi-select-all:before {
+ content: '\f254';
+}
+.zmdi-space-bar:before {
+ content: '\f255';
+}
+.zmdi-text-format:before {
+ content: '\f256';
+}
+.zmdi-transform:before {
+ content: '\f257';
+}
+.zmdi-undo:before {
+ content: '\f258';
+}
+.zmdi-wrap-text:before {
+ content: '\f259';
+}
+.zmdi-comment-alert:before {
+ content: '\f25a';
+}
+.zmdi-comment-alt-text:before {
+ content: '\f25b';
+}
+.zmdi-comment-alt:before {
+ content: '\f25c';
+}
+.zmdi-comment-edit:before {
+ content: '\f25d';
+}
+.zmdi-comment-image:before {
+ content: '\f25e';
+}
+.zmdi-comment-list:before {
+ content: '\f25f';
+}
+.zmdi-comment-more:before {
+ content: '\f260';
+}
+.zmdi-comment-outline:before {
+ content: '\f261';
+}
+.zmdi-comment-text-alt:before {
+ content: '\f262';
+}
+.zmdi-comment-text:before {
+ content: '\f263';
+}
+.zmdi-comment-video:before {
+ content: '\f264';
+}
+.zmdi-comment:before {
+ content: '\f265';
+}
+.zmdi-comments:before {
+ content: '\f266';
+}
+.zmdi-check-all:before {
+ content: '\f267';
+}
+.zmdi-check-circle-u:before {
+ content: '\f268';
+}
+.zmdi-check-circle:before {
+ content: '\f269';
+}
+.zmdi-check-square:before {
+ content: '\f26a';
+}
+.zmdi-check:before {
+ content: '\f26b';
+}
+.zmdi-circle-o:before {
+ content: '\f26c';
+}
+.zmdi-circle:before {
+ content: '\f26d';
+}
+.zmdi-dot-circle-alt:before {
+ content: '\f26e';
+}
+.zmdi-dot-circle:before {
+ content: '\f26f';
+}
+.zmdi-minus-circle-outline:before {
+ content: '\f270';
+}
+.zmdi-minus-circle:before {
+ content: '\f271';
+}
+.zmdi-minus-square:before {
+ content: '\f272';
+}
+.zmdi-minus:before {
+ content: '\f273';
+}
+.zmdi-plus-circle-o-duplicate:before {
+ content: '\f274';
+}
+.zmdi-plus-circle-o:before {
+ content: '\f275';
+}
+.zmdi-plus-circle:before {
+ content: '\f276';
+}
+.zmdi-plus-square:before {
+ content: '\f277';
+}
+.zmdi-plus:before {
+ content: '\f278';
+}
+.zmdi-square-o:before {
+ content: '\f279';
+}
+.zmdi-star-circle:before {
+ content: '\f27a';
+}
+.zmdi-star-half:before {
+ content: '\f27b';
+}
+.zmdi-star-outline:before {
+ content: '\f27c';
+}
+.zmdi-star:before {
+ content: '\f27d';
+}
+.zmdi-bluetooth-connected:before {
+ content: '\f27e';
+}
+.zmdi-bluetooth-off:before {
+ content: '\f27f';
+}
+.zmdi-bluetooth-search:before {
+ content: '\f280';
+}
+.zmdi-bluetooth-setting:before {
+ content: '\f281';
+}
+.zmdi-bluetooth:before {
+ content: '\f282';
+}
+.zmdi-camera-add:before {
+ content: '\f283';
+}
+.zmdi-camera-alt:before {
+ content: '\f284';
+}
+.zmdi-camera-bw:before {
+ content: '\f285';
+}
+.zmdi-camera-front:before {
+ content: '\f286';
+}
+.zmdi-camera-mic:before {
+ content: '\f287';
+}
+.zmdi-camera-party-mode:before {
+ content: '\f288';
+}
+.zmdi-camera-rear:before {
+ content: '\f289';
+}
+.zmdi-camera-roll:before {
+ content: '\f28a';
+}
+.zmdi-camera-switch:before {
+ content: '\f28b';
+}
+.zmdi-camera:before {
+ content: '\f28c';
+}
+.zmdi-card-alert:before {
+ content: '\f28d';
+}
+.zmdi-card-off:before {
+ content: '\f28e';
+}
+.zmdi-card-sd:before {
+ content: '\f28f';
+}
+.zmdi-card-sim:before {
+ content: '\f290';
+}
+.zmdi-desktop-mac:before {
+ content: '\f291';
+}
+.zmdi-desktop-windows:before {
+ content: '\f292';
+}
+.zmdi-device-hub:before {
+ content: '\f293';
+}
+.zmdi-devices-off:before {
+ content: '\f294';
+}
+.zmdi-devices:before {
+ content: '\f295';
+}
+.zmdi-dock:before {
+ content: '\f296';
+}
+.zmdi-floppy:before {
+ content: '\f297';
+}
+.zmdi-gamepad:before {
+ content: '\f298';
+}
+.zmdi-gps-dot:before {
+ content: '\f299';
+}
+.zmdi-gps-off:before {
+ content: '\f29a';
+}
+.zmdi-gps:before {
+ content: '\f29b';
+}
+.zmdi-headset-mic:before {
+ content: '\f29c';
+}
+.zmdi-headset:before {
+ content: '\f29d';
+}
+.zmdi-input-antenna:before {
+ content: '\f29e';
+}
+.zmdi-input-composite:before {
+ content: '\f29f';
+}
+.zmdi-input-hdmi:before {
+ content: '\f2a0';
+}
+.zmdi-input-power:before {
+ content: '\f2a1';
+}
+.zmdi-input-svideo:before {
+ content: '\f2a2';
+}
+.zmdi-keyboard-hide:before {
+ content: '\f2a3';
+}
+.zmdi-keyboard:before {
+ content: '\f2a4';
+}
+.zmdi-laptop-chromebook:before {
+ content: '\f2a5';
+}
+.zmdi-laptop-mac:before {
+ content: '\f2a6';
+}
+.zmdi-laptop:before {
+ content: '\f2a7';
+}
+.zmdi-mic-off:before {
+ content: '\f2a8';
+}
+.zmdi-mic-outline:before {
+ content: '\f2a9';
+}
+.zmdi-mic-setting:before {
+ content: '\f2aa';
+}
+.zmdi-mic:before {
+ content: '\f2ab';
+}
+.zmdi-mouse:before {
+ content: '\f2ac';
+}
+.zmdi-network-alert:before {
+ content: '\f2ad';
+}
+.zmdi-network-locked:before {
+ content: '\f2ae';
+}
+.zmdi-network-off:before {
+ content: '\f2af';
+}
+.zmdi-network-outline:before {
+ content: '\f2b0';
+}
+.zmdi-network-setting:before {
+ content: '\f2b1';
+}
+.zmdi-network:before {
+ content: '\f2b2';
+}
+.zmdi-phone-bluetooth:before {
+ content: '\f2b3';
+}
+.zmdi-phone-end:before {
+ content: '\f2b4';
+}
+.zmdi-phone-forwarded:before {
+ content: '\f2b5';
+}
+.zmdi-phone-in-talk:before {
+ content: '\f2b6';
+}
+.zmdi-phone-locked:before {
+ content: '\f2b7';
+}
+.zmdi-phone-missed:before {
+ content: '\f2b8';
+}
+.zmdi-phone-msg:before {
+ content: '\f2b9';
+}
+.zmdi-phone-paused:before {
+ content: '\f2ba';
+}
+.zmdi-phone-ring:before {
+ content: '\f2bb';
+}
+.zmdi-phone-setting:before {
+ content: '\f2bc';
+}
+.zmdi-phone-sip:before {
+ content: '\f2bd';
+}
+.zmdi-phone:before {
+ content: '\f2be';
+}
+.zmdi-portable-wifi-changes:before {
+ content: '\f2bf';
+}
+.zmdi-portable-wifi-off:before {
+ content: '\f2c0';
+}
+.zmdi-portable-wifi:before {
+ content: '\f2c1';
+}
+.zmdi-radio:before {
+ content: '\f2c2';
+}
+.zmdi-reader:before {
+ content: '\f2c3';
+}
+.zmdi-remote-control-alt:before {
+ content: '\f2c4';
+}
+.zmdi-remote-control:before {
+ content: '\f2c5';
+}
+.zmdi-router:before {
+ content: '\f2c6';
+}
+.zmdi-scanner:before {
+ content: '\f2c7';
+}
+.zmdi-smartphone-android:before {
+ content: '\f2c8';
+}
+.zmdi-smartphone-download:before {
+ content: '\f2c9';
+}
+.zmdi-smartphone-erase:before {
+ content: '\f2ca';
+}
+.zmdi-smartphone-info:before {
+ content: '\f2cb';
+}
+.zmdi-smartphone-iphone:before {
+ content: '\f2cc';
+}
+.zmdi-smartphone-landscape-lock:before {
+ content: '\f2cd';
+}
+.zmdi-smartphone-landscape:before {
+ content: '\f2ce';
+}
+.zmdi-smartphone-lock:before {
+ content: '\f2cf';
+}
+.zmdi-smartphone-portrait-lock:before {
+ content: '\f2d0';
+}
+.zmdi-smartphone-ring:before {
+ content: '\f2d1';
+}
+.zmdi-smartphone-setting:before {
+ content: '\f2d2';
+}
+.zmdi-smartphone-setup:before {
+ content: '\f2d3';
+}
+.zmdi-smartphone:before {
+ content: '\f2d4';
+}
+.zmdi-speaker:before {
+ content: '\f2d5';
+}
+.zmdi-tablet-android:before {
+ content: '\f2d6';
+}
+.zmdi-tablet-mac:before {
+ content: '\f2d7';
+}
+.zmdi-tablet:before {
+ content: '\f2d8';
+}
+.zmdi-tv-alt-play:before {
+ content: '\f2d9';
+}
+.zmdi-tv-list:before {
+ content: '\f2da';
+}
+.zmdi-tv-play:before {
+ content: '\f2db';
+}
+.zmdi-tv:before {
+ content: '\f2dc';
+}
+.zmdi-usb:before {
+ content: '\f2dd';
+}
+.zmdi-videocam-off:before {
+ content: '\f2de';
+}
+.zmdi-videocam-switch:before {
+ content: '\f2df';
+}
+.zmdi-videocam:before {
+ content: '\f2e0';
+}
+.zmdi-watch:before {
+ content: '\f2e1';
+}
+.zmdi-wifi-alt-2:before {
+ content: '\f2e2';
+}
+.zmdi-wifi-alt:before {
+ content: '\f2e3';
+}
+.zmdi-wifi-info:before {
+ content: '\f2e4';
+}
+.zmdi-wifi-lock:before {
+ content: '\f2e5';
+}
+.zmdi-wifi-off:before {
+ content: '\f2e6';
+}
+.zmdi-wifi-outline:before {
+ content: '\f2e7';
+}
+.zmdi-wifi:before {
+ content: '\f2e8';
+}
+.zmdi-arrow-left-bottom:before {
+ content: '\f2e9';
+}
+.zmdi-arrow-left:before {
+ content: '\f2ea';
+}
+.zmdi-arrow-merge:before {
+ content: '\f2eb';
+}
+.zmdi-arrow-missed:before {
+ content: '\f2ec';
+}
+.zmdi-arrow-right-top:before {
+ content: '\f2ed';
+}
+.zmdi-arrow-right:before {
+ content: '\f2ee';
+}
+.zmdi-arrow-split:before {
+ content: '\f2ef';
+}
+.zmdi-arrows:before {
+ content: '\f2f0';
+}
+.zmdi-caret-down-circle:before {
+ content: '\f2f1';
+}
+.zmdi-caret-down:before {
+ content: '\f2f2';
+}
+.zmdi-caret-left-circle:before {
+ content: '\f2f3';
+}
+.zmdi-caret-left:before {
+ content: '\f2f4';
+}
+.zmdi-caret-right-circle:before {
+ content: '\f2f5';
+}
+.zmdi-caret-right:before {
+ content: '\f2f6';
+}
+.zmdi-caret-up-circle:before {
+ content: '\f2f7';
+}
+.zmdi-caret-up:before {
+ content: '\f2f8';
+}
+.zmdi-chevron-down:before {
+ content: '\f2f9';
+}
+.zmdi-chevron-left:before {
+ content: '\f2fa';
+}
+.zmdi-chevron-right:before {
+ content: '\f2fb';
+}
+.zmdi-chevron-up:before {
+ content: '\f2fc';
+}
+.zmdi-forward:before {
+ content: '\f2fd';
+}
+.zmdi-long-arrow-down:before {
+ content: '\f2fe';
+}
+.zmdi-long-arrow-left:before {
+ content: '\f2ff';
+}
+.zmdi-long-arrow-return:before {
+ content: '\f300';
+}
+.zmdi-long-arrow-right:before {
+ content: '\f301';
+}
+.zmdi-long-arrow-tab:before {
+ content: '\f302';
+}
+.zmdi-long-arrow-up:before {
+ content: '\f303';
+}
+.zmdi-rotate-ccw:before {
+ content: '\f304';
+}
+.zmdi-rotate-cw:before {
+ content: '\f305';
+}
+.zmdi-rotate-left:before {
+ content: '\f306';
+}
+.zmdi-rotate-right:before {
+ content: '\f307';
+}
+.zmdi-square-down:before {
+ content: '\f308';
+}
+.zmdi-square-right:before {
+ content: '\f309';
+}
+.zmdi-swap-alt:before {
+ content: '\f30a';
+}
+.zmdi-swap-vertical-circle:before {
+ content: '\f30b';
+}
+.zmdi-swap-vertical:before {
+ content: '\f30c';
+}
+.zmdi-swap:before {
+ content: '\f30d';
+}
+.zmdi-trending-down:before {
+ content: '\f30e';
+}
+.zmdi-trending-flat:before {
+ content: '\f30f';
+}
+.zmdi-trending-up:before {
+ content: '\f310';
+}
+.zmdi-unfold-less:before {
+ content: '\f311';
+}
+.zmdi-unfold-more:before {
+ content: '\f312';
+}
+.zmdi-apps:before {
+ content: '\f313';
+}
+.zmdi-grid-off:before {
+ content: '\f314';
+}
+.zmdi-grid:before {
+ content: '\f315';
+}
+.zmdi-view-agenda:before {
+ content: '\f316';
+}
+.zmdi-view-array:before {
+ content: '\f317';
+}
+.zmdi-view-carousel:before {
+ content: '\f318';
+}
+.zmdi-view-column:before {
+ content: '\f319';
+}
+.zmdi-view-comfy:before {
+ content: '\f31a';
+}
+.zmdi-view-compact:before {
+ content: '\f31b';
+}
+.zmdi-view-dashboard:before {
+ content: '\f31c';
+}
+.zmdi-view-day:before {
+ content: '\f31d';
+}
+.zmdi-view-headline:before {
+ content: '\f31e';
+}
+.zmdi-view-list-alt:before {
+ content: '\f31f';
+}
+.zmdi-view-list:before {
+ content: '\f320';
+}
+.zmdi-view-module:before {
+ content: '\f321';
+}
+.zmdi-view-quilt:before {
+ content: '\f322';
+}
+.zmdi-view-stream:before {
+ content: '\f323';
+}
+.zmdi-view-subtitles:before {
+ content: '\f324';
+}
+.zmdi-view-toc:before {
+ content: '\f325';
+}
+.zmdi-view-web:before {
+ content: '\f326';
+}
+.zmdi-view-week:before {
+ content: '\f327';
+}
+.zmdi-widgets:before {
+ content: '\f328';
+}
+.zmdi-alarm-check:before {
+ content: '\f329';
+}
+.zmdi-alarm-off:before {
+ content: '\f32a';
+}
+.zmdi-alarm-plus:before {
+ content: '\f32b';
+}
+.zmdi-alarm-snooze:before {
+ content: '\f32c';
+}
+.zmdi-alarm:before {
+ content: '\f32d';
+}
+.zmdi-calendar-alt:before {
+ content: '\f32e';
+}
+.zmdi-calendar-check:before {
+ content: '\f32f';
+}
+.zmdi-calendar-close:before {
+ content: '\f330';
+}
+.zmdi-calendar-note:before {
+ content: '\f331';
+}
+.zmdi-calendar:before {
+ content: '\f332';
+}
+.zmdi-time-countdown:before {
+ content: '\f333';
+}
+.zmdi-time-interval:before {
+ content: '\f334';
+}
+.zmdi-time-restore-setting:before {
+ content: '\f335';
+}
+.zmdi-time-restore:before {
+ content: '\f336';
+}
+.zmdi-time:before {
+ content: '\f337';
+}
+.zmdi-timer-off:before {
+ content: '\f338';
+}
+.zmdi-timer:before {
+ content: '\f339';
+}
+.zmdi-android-alt:before {
+ content: '\f33a';
+}
+.zmdi-android:before {
+ content: '\f33b';
+}
+.zmdi-apple:before {
+ content: '\f33c';
+}
+.zmdi-behance:before {
+ content: '\f33d';
+}
+.zmdi-codepen:before {
+ content: '\f33e';
+}
+.zmdi-dribbble:before {
+ content: '\f33f';
+}
+.zmdi-dropbox:before {
+ content: '\f340';
+}
+.zmdi-evernote:before {
+ content: '\f341';
+}
+.zmdi-facebook-box:before {
+ content: '\f342';
+}
+.zmdi-facebook:before {
+ content: '\f343';
+}
+.zmdi-github-box:before {
+ content: '\f344';
+}
+.zmdi-github:before {
+ content: '\f345';
+}
+.zmdi-google-drive:before {
+ content: '\f346';
+}
+.zmdi-google-earth:before {
+ content: '\f347';
+}
+.zmdi-google-glass:before {
+ content: '\f348';
+}
+.zmdi-google-maps:before {
+ content: '\f349';
+}
+.zmdi-google-pages:before {
+ content: '\f34a';
+}
+.zmdi-google-play:before {
+ content: '\f34b';
+}
+.zmdi-google-plus-box:before {
+ content: '\f34c';
+}
+.zmdi-google-plus:before {
+ content: '\f34d';
+}
+.zmdi-google:before {
+ content: '\f34e';
+}
+.zmdi-instagram:before {
+ content: '\f34f';
+}
+.zmdi-language-css3:before {
+ content: '\f350';
+}
+.zmdi-language-html5:before {
+ content: '\f351';
+}
+.zmdi-language-javascript:before {
+ content: '\f352';
+}
+.zmdi-language-python-alt:before {
+ content: '\f353';
+}
+.zmdi-language-python:before {
+ content: '\f354';
+}
+.zmdi-lastfm:before {
+ content: '\f355';
+}
+.zmdi-linkedin-box:before {
+ content: '\f356';
+}
+.zmdi-paypal:before {
+ content: '\f357';
+}
+.zmdi-pinterest-box:before {
+ content: '\f358';
+}
+.zmdi-pocket:before {
+ content: '\f359';
+}
+.zmdi-polymer:before {
+ content: '\f35a';
+}
+.zmdi-share:before {
+ content: '\f35b';
+}
+.zmdi-stackoverflow:before {
+ content: '\f35c';
+}
+.zmdi-steam-square:before {
+ content: '\f35d';
+}
+.zmdi-steam:before {
+ content: '\f35e';
+}
+.zmdi-twitter-box:before {
+ content: '\f35f';
+}
+.zmdi-twitter:before {
+ content: '\f360';
+}
+.zmdi-vk:before {
+ content: '\f361';
+}
+.zmdi-wikipedia:before {
+ content: '\f362';
+}
+.zmdi-windows:before {
+ content: '\f363';
+}
+.zmdi-aspect-ratio-alt:before {
+ content: '\f364';
+}
+.zmdi-aspect-ratio:before {
+ content: '\f365';
+}
+.zmdi-blur-circular:before {
+ content: '\f366';
+}
+.zmdi-blur-linear:before {
+ content: '\f367';
+}
+.zmdi-blur-off:before {
+ content: '\f368';
+}
+.zmdi-blur:before {
+ content: '\f369';
+}
+.zmdi-brightness-2:before {
+ content: '\f36a';
+}
+.zmdi-brightness-3:before {
+ content: '\f36b';
+}
+.zmdi-brightness-4:before {
+ content: '\f36c';
+}
+.zmdi-brightness-5:before {
+ content: '\f36d';
+}
+.zmdi-brightness-6:before {
+ content: '\f36e';
+}
+.zmdi-brightness-7:before {
+ content: '\f36f';
+}
+.zmdi-brightness-auto:before {
+ content: '\f370';
+}
+.zmdi-brightness-setting:before {
+ content: '\f371';
+}
+.zmdi-broken-image:before {
+ content: '\f372';
+}
+.zmdi-center-focus-strong:before {
+ content: '\f373';
+}
+.zmdi-center-focus-weak:before {
+ content: '\f374';
+}
+.zmdi-compare:before {
+ content: '\f375';
+}
+.zmdi-crop-16-9:before {
+ content: '\f376';
+}
+.zmdi-crop-3-2:before {
+ content: '\f377';
+}
+.zmdi-crop-5-4:before {
+ content: '\f378';
+}
+.zmdi-crop-7-5:before {
+ content: '\f379';
+}
+.zmdi-crop-din:before {
+ content: '\f37a';
+}
+.zmdi-crop-free:before {
+ content: '\f37b';
+}
+.zmdi-crop-landscape:before {
+ content: '\f37c';
+}
+.zmdi-crop-portrait:before {
+ content: '\f37d';
+}
+.zmdi-crop-square:before {
+ content: '\f37e';
+}
+.zmdi-exposure-alt:before {
+ content: '\f37f';
+}
+.zmdi-exposure:before {
+ content: '\f380';
+}
+.zmdi-filter-b-and-w:before {
+ content: '\f381';
+}
+.zmdi-filter-center-focus:before {
+ content: '\f382';
+}
+.zmdi-filter-frames:before {
+ content: '\f383';
+}
+.zmdi-filter-tilt-shift:before {
+ content: '\f384';
+}
+.zmdi-gradient:before {
+ content: '\f385';
+}
+.zmdi-grain:before {
+ content: '\f386';
+}
+.zmdi-graphic-eq:before {
+ content: '\f387';
+}
+.zmdi-hdr-off:before {
+ content: '\f388';
+}
+.zmdi-hdr-strong:before {
+ content: '\f389';
+}
+.zmdi-hdr-weak:before {
+ content: '\f38a';
+}
+.zmdi-hdr:before {
+ content: '\f38b';
+}
+.zmdi-iridescent:before {
+ content: '\f38c';
+}
+.zmdi-leak-off:before {
+ content: '\f38d';
+}
+.zmdi-leak:before {
+ content: '\f38e';
+}
+.zmdi-looks:before {
+ content: '\f38f';
+}
+.zmdi-loupe:before {
+ content: '\f390';
+}
+.zmdi-panorama-horizontal:before {
+ content: '\f391';
+}
+.zmdi-panorama-vertical:before {
+ content: '\f392';
+}
+.zmdi-panorama-wide-angle:before {
+ content: '\f393';
+}
+.zmdi-photo-size-select-large:before {
+ content: '\f394';
+}
+.zmdi-photo-size-select-small:before {
+ content: '\f395';
+}
+.zmdi-picture-in-picture:before {
+ content: '\f396';
+}
+.zmdi-slideshow:before {
+ content: '\f397';
+}
+.zmdi-texture:before {
+ content: '\f398';
+}
+.zmdi-tonality:before {
+ content: '\f399';
+}
+.zmdi-vignette:before {
+ content: '\f39a';
+}
+.zmdi-wb-auto:before {
+ content: '\f39b';
+}
+.zmdi-eject-alt:before {
+ content: '\f39c';
+}
+.zmdi-eject:before {
+ content: '\f39d';
+}
+.zmdi-equalizer:before {
+ content: '\f39e';
+}
+.zmdi-fast-forward:before {
+ content: '\f39f';
+}
+.zmdi-fast-rewind:before {
+ content: '\f3a0';
+}
+.zmdi-forward-10:before {
+ content: '\f3a1';
+}
+.zmdi-forward-30:before {
+ content: '\f3a2';
+}
+.zmdi-forward-5:before {
+ content: '\f3a3';
+}
+.zmdi-hearing:before {
+ content: '\f3a4';
+}
+.zmdi-pause-circle-outline:before {
+ content: '\f3a5';
+}
+.zmdi-pause-circle:before {
+ content: '\f3a6';
+}
+.zmdi-pause:before {
+ content: '\f3a7';
+}
+.zmdi-play-circle-outline:before {
+ content: '\f3a8';
+}
+.zmdi-play-circle:before {
+ content: '\f3a9';
+}
+.zmdi-play:before {
+ content: '\f3aa';
+}
+.zmdi-playlist-audio:before {
+ content: '\f3ab';
+}
+.zmdi-playlist-plus:before {
+ content: '\f3ac';
+}
+.zmdi-repeat-one:before {
+ content: '\f3ad';
+}
+.zmdi-repeat:before {
+ content: '\f3ae';
+}
+.zmdi-replay-10:before {
+ content: '\f3af';
+}
+.zmdi-replay-30:before {
+ content: '\f3b0';
+}
+.zmdi-replay-5:before {
+ content: '\f3b1';
+}
+.zmdi-replay:before {
+ content: '\f3b2';
+}
+.zmdi-shuffle:before {
+ content: '\f3b3';
+}
+.zmdi-skip-next:before {
+ content: '\f3b4';
+}
+.zmdi-skip-previous:before {
+ content: '\f3b5';
+}
+.zmdi-stop:before {
+ content: '\f3b6';
+}
+.zmdi-surround-sound:before {
+ content: '\f3b7';
+}
+.zmdi-tune:before {
+ content: '\f3b8';
+}
+.zmdi-volume-down:before {
+ content: '\f3b9';
+}
+.zmdi-volume-mute:before {
+ content: '\f3ba';
+}
+.zmdi-volume-off:before {
+ content: '\f3bb';
+}
+.zmdi-volume-up:before {
+ content: '\f3bc';
+}
+.zmdi-n-1-square:before {
+ content: '\f3bd';
+}
+.zmdi-n-2-square:before {
+ content: '\f3be';
+}
+.zmdi-n-3-square:before {
+ content: '\f3bf';
+}
+.zmdi-n-4-square:before {
+ content: '\f3c0';
+}
+.zmdi-n-5-square:before {
+ content: '\f3c1';
+}
+.zmdi-n-6-square:before {
+ content: '\f3c2';
+}
+.zmdi-neg-1:before {
+ content: '\f3c3';
+}
+.zmdi-neg-2:before {
+ content: '\f3c4';
+}
+.zmdi-plus-1:before {
+ content: '\f3c5';
+}
+.zmdi-plus-2:before {
+ content: '\f3c6';
+}
+.zmdi-sec-10:before {
+ content: '\f3c7';
+}
+.zmdi-sec-3:before {
+ content: '\f3c8';
+}
+.zmdi-zero:before {
+ content: '\f3c9';
+}
+.zmdi-airline-seat-flat-angled:before {
+ content: '\f3ca';
+}
+.zmdi-airline-seat-flat:before {
+ content: '\f3cb';
+}
+.zmdi-airline-seat-individual-suite:before {
+ content: '\f3cc';
+}
+.zmdi-airline-seat-legroom-extra:before {
+ content: '\f3cd';
+}
+.zmdi-airline-seat-legroom-normal:before {
+ content: '\f3ce';
+}
+.zmdi-airline-seat-legroom-reduced:before {
+ content: '\f3cf';
+}
+.zmdi-airline-seat-recline-extra:before {
+ content: '\f3d0';
+}
+.zmdi-airline-seat-recline-normal:before {
+ content: '\f3d1';
+}
+.zmdi-airplay:before {
+ content: '\f3d2';
+}
+.zmdi-closed-caption:before {
+ content: '\f3d3';
+}
+.zmdi-confirmation-number:before {
+ content: '\f3d4';
+}
+.zmdi-developer-board:before {
+ content: '\f3d5';
+}
+.zmdi-disc-full:before {
+ content: '\f3d6';
+}
+.zmdi-explicit:before {
+ content: '\f3d7';
+}
+.zmdi-flight-land:before {
+ content: '\f3d8';
+}
+.zmdi-flight-takeoff:before {
+ content: '\f3d9';
+}
+.zmdi-flip-to-back:before {
+ content: '\f3da';
+}
+.zmdi-flip-to-front:before {
+ content: '\f3db';
+}
+.zmdi-group-work:before {
+ content: '\f3dc';
+}
+.zmdi-hd:before {
+ content: '\f3dd';
+}
+.zmdi-hq:before {
+ content: '\f3de';
+}
+.zmdi-markunread-mailbox:before {
+ content: '\f3df';
+}
+.zmdi-memory:before {
+ content: '\f3e0';
+}
+.zmdi-nfc:before {
+ content: '\f3e1';
+}
+.zmdi-play-for-work:before {
+ content: '\f3e2';
+}
+.zmdi-power-input:before {
+ content: '\f3e3';
+}
+.zmdi-present-to-all:before {
+ content: '\f3e4';
+}
+.zmdi-satellite:before {
+ content: '\f3e5';
+}
+.zmdi-tap-and-play:before {
+ content: '\f3e6';
+}
+.zmdi-vibration:before {
+ content: '\f3e7';
+}
+.zmdi-voicemail:before {
+ content: '\f3e8';
+}
+.zmdi-group:before {
+ content: '\f3e9';
+}
+.zmdi-rss:before {
+ content: '\f3ea';
+}
+.zmdi-shape:before {
+ content: '\f3eb';
+}
+.zmdi-spinner:before {
+ content: '\f3ec';
+}
+.zmdi-ungroup:before {
+ content: '\f3ed';
+}
+.zmdi-500px:before {
+ content: '\f3ee';
+}
+.zmdi-8tracks:before {
+ content: '\f3ef';
+}
+.zmdi-amazon:before {
+ content: '\f3f0';
+}
+.zmdi-blogger:before {
+ content: '\f3f1';
+}
+.zmdi-delicious:before {
+ content: '\f3f2';
+}
+.zmdi-disqus:before {
+ content: '\f3f3';
+}
+.zmdi-flattr:before {
+ content: '\f3f4';
+}
+.zmdi-flickr:before {
+ content: '\f3f5';
+}
+.zmdi-github-alt:before {
+ content: '\f3f6';
+}
+.zmdi-google-old:before {
+ content: '\f3f7';
+}
+.zmdi-linkedin:before {
+ content: '\f3f8';
+}
+.zmdi-odnoklassniki:before {
+ content: '\f3f9';
+}
+.zmdi-outlook:before {
+ content: '\f3fa';
+}
+.zmdi-paypal-alt:before {
+ content: '\f3fb';
+}
+.zmdi-pinterest:before {
+ content: '\f3fc';
+}
+.zmdi-playstation:before {
+ content: '\f3fd';
+}
+.zmdi-reddit:before {
+ content: '\f3fe';
+}
+.zmdi-skype:before {
+ content: '\f3ff';
+}
+.zmdi-slideshare:before {
+ content: '\f400';
+}
+.zmdi-soundcloud:before {
+ content: '\f401';
+}
+.zmdi-tumblr:before {
+ content: '\f402';
+}
+.zmdi-twitch:before {
+ content: '\f403';
+}
+.zmdi-vimeo:before {
+ content: '\f404';
+}
+.zmdi-whatsapp:before {
+ content: '\f405';
+}
+.zmdi-xbox:before {
+ content: '\f406';
+}
+.zmdi-yahoo:before {
+ content: '\f407';
+}
+.zmdi-youtube-play:before {
+ content: '\f408';
+}
+.zmdi-youtube:before {
+ content: '\f409';
+}
+.zmdi-3d-rotation:before {
+ content: '\f101';
+}
+.zmdi-airplane-off:before {
+ content: '\f102';
+}
+.zmdi-airplane:before {
+ content: '\f103';
+}
+.zmdi-album:before {
+ content: '\f104';
+}
+.zmdi-archive:before {
+ content: '\f105';
+}
+.zmdi-assignment-account:before {
+ content: '\f106';
+}
+.zmdi-assignment-alert:before {
+ content: '\f107';
+}
+.zmdi-assignment-check:before {
+ content: '\f108';
+}
+.zmdi-assignment-o:before {
+ content: '\f109';
+}
+.zmdi-assignment-return:before {
+ content: '\f10a';
+}
+.zmdi-assignment-returned:before {
+ content: '\f10b';
+}
+.zmdi-assignment:before {
+ content: '\f10c';
+}
+.zmdi-attachment-alt:before {
+ content: '\f10d';
+}
+.zmdi-attachment:before {
+ content: '\f10e';
+}
+.zmdi-audio:before {
+ content: '\f10f';
+}
+.zmdi-badge-check:before {
+ content: '\f110';
+}
+.zmdi-balance-wallet:before {
+ content: '\f111';
+}
+.zmdi-balance:before {
+ content: '\f112';
+}
+.zmdi-battery-alert:before {
+ content: '\f113';
+}
+.zmdi-battery-flash:before {
+ content: '\f114';
+}
+.zmdi-battery-unknown:before {
+ content: '\f115';
+}
+.zmdi-battery:before {
+ content: '\f116';
+}
+.zmdi-bike:before {
+ content: '\f117';
+}
+.zmdi-block-alt:before {
+ content: '\f118';
+}
+.zmdi-block:before {
+ content: '\f119';
+}
+.zmdi-boat:before {
+ content: '\f11a';
+}
+.zmdi-book-image:before {
+ content: '\f11b';
+}
+.zmdi-book:before {
+ content: '\f11c';
+}
+.zmdi-bookmark-outline:before {
+ content: '\f11d';
+}
+.zmdi-bookmark:before {
+ content: '\f11e';
+}
+.zmdi-brush:before {
+ content: '\f11f';
+}
+.zmdi-bug:before {
+ content: '\f120';
+}
+.zmdi-bus:before {
+ content: '\f121';
+}
+.zmdi-cake:before {
+ content: '\f122';
+}
+.zmdi-car-taxi:before {
+ content: '\f123';
+}
+.zmdi-car-wash:before {
+ content: '\f124';
+}
+.zmdi-car:before {
+ content: '\f125';
+}
+.zmdi-card-giftcard:before {
+ content: '\f126';
+}
+.zmdi-card-membership:before {
+ content: '\f127';
+}
+.zmdi-card-travel:before {
+ content: '\f128';
+}
+.zmdi-card:before {
+ content: '\f129';
+}
+.zmdi-case-check:before {
+ content: '\f12a';
+}
+.zmdi-case-download:before {
+ content: '\f12b';
+}
+.zmdi-case-play:before {
+ content: '\f12c';
+}
+.zmdi-case:before {
+ content: '\f12d';
+}
+.zmdi-cast-connected:before {
+ content: '\f12e';
+}
+.zmdi-cast:before {
+ content: '\f12f';
+}
+.zmdi-chart-donut:before {
+ content: '\f130';
+}
+.zmdi-chart:before {
+ content: '\f131';
+}
+.zmdi-city-alt:before {
+ content: '\f132';
+}
+.zmdi-city:before {
+ content: '\f133';
+}
+.zmdi-close-circle-o:before {
+ content: '\f134';
+}
+.zmdi-close-circle:before {
+ content: '\f135';
+}
+.zmdi-close:before {
+ content: '\f136';
+}
+.zmdi-cocktail:before {
+ content: '\f137';
+}
+.zmdi-code-setting:before {
+ content: '\f138';
+}
+.zmdi-code-smartphone:before {
+ content: '\f139';
+}
+.zmdi-code:before {
+ content: '\f13a';
+}
+.zmdi-coffee:before {
+ content: '\f13b';
+}
+.zmdi-collection-bookmark:before {
+ content: '\f13c';
+}
+.zmdi-collection-case-play:before {
+ content: '\f13d';
+}
+.zmdi-collection-folder-image:before {
+ content: '\f13e';
+}
+.zmdi-collection-image-o:before {
+ content: '\f13f';
+}
+.zmdi-collection-image:before {
+ content: '\f140';
+}
+.zmdi-collection-item-1:before {
+ content: '\f141';
+}
+.zmdi-collection-item-2:before {
+ content: '\f142';
+}
+.zmdi-collection-item-3:before {
+ content: '\f143';
+}
+.zmdi-collection-item-4:before {
+ content: '\f144';
+}
+.zmdi-collection-item-5:before {
+ content: '\f145';
+}
+.zmdi-collection-item-6:before {
+ content: '\f146';
+}
+.zmdi-collection-item-7:before {
+ content: '\f147';
+}
+.zmdi-collection-item-8:before {
+ content: '\f148';
+}
+.zmdi-collection-item-9-plus:before {
+ content: '\f149';
+}
+.zmdi-collection-item-9:before {
+ content: '\f14a';
+}
+.zmdi-collection-item:before {
+ content: '\f14b';
+}
+.zmdi-collection-music:before {
+ content: '\f14c';
+}
+.zmdi-collection-pdf:before {
+ content: '\f14d';
+}
+.zmdi-collection-plus:before {
+ content: '\f14e';
+}
+.zmdi-collection-speaker:before {
+ content: '\f14f';
+}
+.zmdi-collection-text:before {
+ content: '\f150';
+}
+.zmdi-collection-video:before {
+ content: '\f151';
+}
+.zmdi-compass:before {
+ content: '\f152';
+}
+.zmdi-cutlery:before {
+ content: '\f153';
+}
+.zmdi-delete:before {
+ content: '\f154';
+}
+.zmdi-dialpad:before {
+ content: '\f155';
+}
+.zmdi-dns:before {
+ content: '\f156';
+}
+.zmdi-drink:before {
+ content: '\f157';
+}
+.zmdi-edit:before {
+ content: '\f158';
+}
+.zmdi-email-open:before {
+ content: '\f159';
+}
+.zmdi-email:before {
+ content: '\f15a';
+}
+.zmdi-eye-off:before {
+ content: '\f15b';
+}
+.zmdi-eye:before {
+ content: '\f15c';
+}
+.zmdi-eyedropper:before {
+ content: '\f15d';
+}
+.zmdi-favorite-outline:before {
+ content: '\f15e';
+}
+.zmdi-favorite:before {
+ content: '\f15f';
+}
+.zmdi-filter-list:before {
+ content: '\f160';
+}
+.zmdi-fire:before {
+ content: '\f161';
+}
+.zmdi-flag:before {
+ content: '\f162';
+}
+.zmdi-flare:before {
+ content: '\f163';
+}
+.zmdi-flash-auto:before {
+ content: '\f164';
+}
+.zmdi-flash-off:before {
+ content: '\f165';
+}
+.zmdi-flash:before {
+ content: '\f166';
+}
+.zmdi-flip:before {
+ content: '\f167';
+}
+.zmdi-flower-alt:before {
+ content: '\f168';
+}
+.zmdi-flower:before {
+ content: '\f169';
+}
+.zmdi-font:before {
+ content: '\f16a';
+}
+.zmdi-fullscreen-alt:before {
+ content: '\f16b';
+}
+.zmdi-fullscreen-exit:before {
+ content: '\f16c';
+}
+.zmdi-fullscreen:before {
+ content: '\f16d';
+}
+.zmdi-functions:before {
+ content: '\f16e';
+}
+.zmdi-gas-station:before {
+ content: '\f16f';
+}
+.zmdi-gesture:before {
+ content: '\f170';
+}
+.zmdi-globe-alt:before {
+ content: '\f171';
+}
+.zmdi-globe-lock:before {
+ content: '\f172';
+}
+.zmdi-globe:before {
+ content: '\f173';
+}
+.zmdi-graduation-cap:before {
+ content: '\f174';
+}
+.zmdi-home:before {
+ content: '\f175';
+}
+.zmdi-hospital-alt:before {
+ content: '\f176';
+}
+.zmdi-hospital:before {
+ content: '\f177';
+}
+.zmdi-hotel:before {
+ content: '\f178';
+}
+.zmdi-hourglass-alt:before {
+ content: '\f179';
+}
+.zmdi-hourglass-outline:before {
+ content: '\f17a';
+}
+.zmdi-hourglass:before {
+ content: '\f17b';
+}
+.zmdi-http:before {
+ content: '\f17c';
+}
+.zmdi-image-alt:before {
+ content: '\f17d';
+}
+.zmdi-image-o:before {
+ content: '\f17e';
+}
+.zmdi-image:before {
+ content: '\f17f';
+}
+.zmdi-inbox:before {
+ content: '\f180';
+}
+.zmdi-invert-colors-off:before {
+ content: '\f181';
+}
+.zmdi-invert-colors:before {
+ content: '\f182';
+}
+.zmdi-key:before {
+ content: '\f183';
+}
+.zmdi-label-alt-outline:before {
+ content: '\f184';
+}
+.zmdi-label-alt:before {
+ content: '\f185';
+}
+.zmdi-label-heart:before {
+ content: '\f186';
+}
+.zmdi-label:before {
+ content: '\f187';
+}
+.zmdi-labels:before {
+ content: '\f188';
+}
+.zmdi-lamp:before {
+ content: '\f189';
+}
+.zmdi-landscape:before {
+ content: '\f18a';
+}
+.zmdi-layers-off:before {
+ content: '\f18b';
+}
+.zmdi-layers:before {
+ content: '\f18c';
+}
+.zmdi-library:before {
+ content: '\f18d';
+}
+.zmdi-link:before {
+ content: '\f18e';
+}
+.zmdi-lock-open:before {
+ content: '\f18f';
+}
+.zmdi-lock-outline:before {
+ content: '\f190';
+}
+.zmdi-lock:before {
+ content: '\f191';
+}
+.zmdi-mail-reply-all:before {
+ content: '\f192';
+}
+.zmdi-mail-reply:before {
+ content: '\f193';
+}
+.zmdi-mail-send:before {
+ content: '\f194';
+}
+.zmdi-mall:before {
+ content: '\f195';
+}
+.zmdi-map:before {
+ content: '\f196';
+}
+.zmdi-menu:before {
+ content: '\f197';
+}
+.zmdi-money-box:before {
+ content: '\f198';
+}
+.zmdi-money-off:before {
+ content: '\f199';
+}
+.zmdi-money:before {
+ content: '\f19a';
+}
+.zmdi-more-vert:before {
+ content: '\f19b';
+}
+.zmdi-more:before {
+ content: '\f19c';
+}
+.zmdi-movie-alt:before {
+ content: '\f19d';
+}
+.zmdi-movie:before {
+ content: '\f19e';
+}
+.zmdi-nature-people:before {
+ content: '\f19f';
+}
+.zmdi-nature:before {
+ content: '\f1a0';
+}
+.zmdi-navigation:before {
+ content: '\f1a1';
+}
+.zmdi-open-in-browser:before {
+ content: '\f1a2';
+}
+.zmdi-open-in-new:before {
+ content: '\f1a3';
+}
+.zmdi-palette:before {
+ content: '\f1a4';
+}
+.zmdi-parking:before {
+ content: '\f1a5';
+}
+.zmdi-pin-account:before {
+ content: '\f1a6';
+}
+.zmdi-pin-assistant:before {
+ content: '\f1a7';
+}
+.zmdi-pin-drop:before {
+ content: '\f1a8';
+}
+.zmdi-pin-help:before {
+ content: '\f1a9';
+}
+.zmdi-pin-off:before {
+ content: '\f1aa';
+}
+.zmdi-pin:before {
+ content: '\f1ab';
+}
+.zmdi-pizza:before {
+ content: '\f1ac';
+}
+.zmdi-plaster:before {
+ content: '\f1ad';
+}
+.zmdi-power-setting:before {
+ content: '\f1ae';
+}
+.zmdi-power:before {
+ content: '\f1af';
+}
+.zmdi-print:before {
+ content: '\f1b0';
+}
+.zmdi-puzzle-piece:before {
+ content: '\f1b1';
+}
+.zmdi-quote:before {
+ content: '\f1b2';
+}
+.zmdi-railway:before {
+ content: '\f1b3';
+}
+.zmdi-receipt:before {
+ content: '\f1b4';
+}
+.zmdi-refresh-alt:before {
+ content: '\f1b5';
+}
+.zmdi-refresh-sync-alert:before {
+ content: '\f1b6';
+}
+.zmdi-refresh-sync-off:before {
+ content: '\f1b7';
+}
+.zmdi-refresh-sync:before {
+ content: '\f1b8';
+}
+.zmdi-refresh:before {
+ content: '\f1b9';
+}
+.zmdi-roller:before {
+ content: '\f1ba';
+}
+.zmdi-ruler:before {
+ content: '\f1bb';
+}
+.zmdi-scissors:before {
+ content: '\f1bc';
+}
+.zmdi-screen-rotation-lock:before {
+ content: '\f1bd';
+}
+.zmdi-screen-rotation:before {
+ content: '\f1be';
+}
+.zmdi-search-for:before {
+ content: '\f1bf';
+}
+.zmdi-search-in-file:before {
+ content: '\f1c0';
+}
+.zmdi-search-in-page:before {
+ content: '\f1c1';
+}
+.zmdi-search-replace:before {
+ content: '\f1c2';
+}
+.zmdi-search:before {
+ content: '\f1c3';
+}
+.zmdi-seat:before {
+ content: '\f1c4';
+}
+.zmdi-settings-square:before {
+ content: '\f1c5';
+}
+.zmdi-settings:before {
+ content: '\f1c6';
+}
+.zmdi-shield-check:before {
+ content: '\f1c7';
+}
+.zmdi-shield-security:before {
+ content: '\f1c8';
+}
+.zmdi-shopping-basket:before {
+ content: '\f1c9';
+}
+.zmdi-shopping-cart-plus:before {
+ content: '\f1ca';
+}
+.zmdi-shopping-cart:before {
+ content: '\f1cb';
+}
+.zmdi-sign-in:before {
+ content: '\f1cc';
+}
+.zmdi-sort-amount-asc:before {
+ content: '\f1cd';
+}
+.zmdi-sort-amount-desc:before {
+ content: '\f1ce';
+}
+.zmdi-sort-asc:before {
+ content: '\f1cf';
+}
+.zmdi-sort-desc:before {
+ content: '\f1d0';
+}
+.zmdi-spellcheck:before {
+ content: '\f1d1';
+}
+.zmdi-storage:before {
+ content: '\f1d2';
+}
+.zmdi-store-24:before {
+ content: '\f1d3';
+}
+.zmdi-store:before {
+ content: '\f1d4';
+}
+.zmdi-subway:before {
+ content: '\f1d5';
+}
+.zmdi-sun:before {
+ content: '\f1d6';
+}
+.zmdi-tab-unselected:before {
+ content: '\f1d7';
+}
+.zmdi-tab:before {
+ content: '\f1d8';
+}
+.zmdi-tag-close:before {
+ content: '\f1d9';
+}
+.zmdi-tag-more:before {
+ content: '\f1da';
+}
+.zmdi-tag:before {
+ content: '\f1db';
+}
+.zmdi-thumb-down:before {
+ content: '\f1dc';
+}
+.zmdi-thumb-up-down:before {
+ content: '\f1dd';
+}
+.zmdi-thumb-up:before {
+ content: '\f1de';
+}
+.zmdi-ticket-star:before {
+ content: '\f1df';
+}
+.zmdi-toll:before {
+ content: '\f1e0';
+}
+.zmdi-toys:before {
+ content: '\f1e1';
+}
+.zmdi-traffic:before {
+ content: '\f1e2';
+}
+.zmdi-translate:before {
+ content: '\f1e3';
+}
+.zmdi-triangle-down:before {
+ content: '\f1e4';
+}
+.zmdi-triangle-up:before {
+ content: '\f1e5';
+}
+.zmdi-truck:before {
+ content: '\f1e6';
+}
+.zmdi-turning-sign:before {
+ content: '\f1e7';
+}
+.zmdi-wallpaper:before {
+ content: '\f1e8';
+}
+.zmdi-washing-machine:before {
+ content: '\f1e9';
+}
+.zmdi-window-maximize:before {
+ content: '\f1ea';
+}
+.zmdi-window-minimize:before {
+ content: '\f1eb';
+}
+.zmdi-window-restore:before {
+ content: '\f1ec';
+}
+.zmdi-wrench:before {
+ content: '\f1ed';
+}
+.zmdi-zoom-in:before {
+ content: '\f1ee';
+}
+.zmdi-zoom-out:before {
+ content: '\f1ef';
+}
+.zmdi-alert-circle-o:before {
+ content: '\f1f0';
+}
+.zmdi-alert-circle:before {
+ content: '\f1f1';
+}
+.zmdi-alert-octagon:before {
+ content: '\f1f2';
+}
+.zmdi-alert-polygon:before {
+ content: '\f1f3';
+}
+.zmdi-alert-triangle:before {
+ content: '\f1f4';
+}
+.zmdi-help-outline:before {
+ content: '\f1f5';
+}
+.zmdi-help:before {
+ content: '\f1f6';
+}
+.zmdi-info-outline:before {
+ content: '\f1f7';
+}
+.zmdi-info:before {
+ content: '\f1f8';
+}
+.zmdi-notifications-active:before {
+ content: '\f1f9';
+}
+.zmdi-notifications-add:before {
+ content: '\f1fa';
+}
+.zmdi-notifications-none:before {
+ content: '\f1fb';
+}
+.zmdi-notifications-off:before {
+ content: '\f1fc';
+}
+.zmdi-notifications-paused:before {
+ content: '\f1fd';
+}
+.zmdi-notifications:before {
+ content: '\f1fe';
+}
+.zmdi-account-add:before {
+ content: '\f1ff';
+}
+.zmdi-account-box-mail:before {
+ content: '\f200';
+}
+.zmdi-account-box-o:before {
+ content: '\f201';
+}
+.zmdi-account-box-phone:before {
+ content: '\f202';
+}
+.zmdi-account-box:before {
+ content: '\f203';
+}
+.zmdi-account-calendar:before {
+ content: '\f204';
+}
+.zmdi-account-circle:before {
+ content: '\f205';
+}
+.zmdi-account-o:before {
+ content: '\f206';
+}
+.zmdi-account:before {
+ content: '\f207';
+}
+.zmdi-accounts-add:before {
+ content: '\f208';
+}
+.zmdi-accounts-alt:before {
+ content: '\f209';
+}
+.zmdi-accounts-list-alt:before {
+ content: '\f20a';
+}
+.zmdi-accounts-list:before {
+ content: '\f20b';
+}
+.zmdi-accounts-outline:before {
+ content: '\f20c';
+}
+.zmdi-accounts:before {
+ content: '\f20d';
+}
+.zmdi-face:before {
+ content: '\f20e';
+}
+.zmdi-female:before {
+ content: '\f20f';
+}
+.zmdi-male-alt:before {
+ content: '\f210';
+}
+.zmdi-male-female:before {
+ content: '\f211';
+}
+.zmdi-male:before {
+ content: '\f212';
+}
+.zmdi-mood-bad:before {
+ content: '\f213';
+}
+.zmdi-mood:before {
+ content: '\f214';
+}
+.zmdi-run:before {
+ content: '\f215';
+}
+.zmdi-walk:before {
+ content: '\f216';
+}
+.zmdi-cloud-box:before {
+ content: '\f217';
+}
+.zmdi-cloud-circle:before {
+ content: '\f218';
+}
+.zmdi-cloud-done:before {
+ content: '\f219';
+}
+.zmdi-cloud-download:before {
+ content: '\f21a';
+}
+.zmdi-cloud-off:before {
+ content: '\f21b';
+}
+.zmdi-cloud-outline-alt:before {
+ content: '\f21c';
+}
+.zmdi-cloud-outline:before {
+ content: '\f21d';
+}
+.zmdi-cloud-upload:before {
+ content: '\f21e';
+}
+.zmdi-cloud:before {
+ content: '\f21f';
+}
+.zmdi-download:before {
+ content: '\f220';
+}
+.zmdi-file-plus:before {
+ content: '\f221';
+}
+.zmdi-file-text:before {
+ content: '\f222';
+}
+.zmdi-file:before {
+ content: '\f223';
+}
+.zmdi-folder-outline:before {
+ content: '\f224';
+}
+.zmdi-folder-person:before {
+ content: '\f225';
+}
+.zmdi-folder-star-alt:before {
+ content: '\f226';
+}
+.zmdi-folder-star:before {
+ content: '\f227';
+}
+.zmdi-folder:before {
+ content: '\f228';
+}
+.zmdi-gif:before {
+ content: '\f229';
+}
+.zmdi-upload:before {
+ content: '\f22a';
+}
+.zmdi-border-all:before {
+ content: '\f22b';
+}
+.zmdi-border-bottom:before {
+ content: '\f22c';
+}
+.zmdi-border-clear:before {
+ content: '\f22d';
+}
+.zmdi-border-color:before {
+ content: '\f22e';
+}
+.zmdi-border-horizontal:before {
+ content: '\f22f';
+}
+.zmdi-border-inner:before {
+ content: '\f230';
+}
+.zmdi-border-left:before {
+ content: '\f231';
+}
+.zmdi-border-outer:before {
+ content: '\f232';
+}
+.zmdi-border-right:before {
+ content: '\f233';
+}
+.zmdi-border-style:before {
+ content: '\f234';
+}
+.zmdi-border-top:before {
+ content: '\f235';
+}
+.zmdi-border-vertical:before {
+ content: '\f236';
+}
+.zmdi-copy:before {
+ content: '\f237';
+}
+.zmdi-crop:before {
+ content: '\f238';
+}
+.zmdi-format-align-center:before {
+ content: '\f239';
+}
+.zmdi-format-align-justify:before {
+ content: '\f23a';
+}
+.zmdi-format-align-left:before {
+ content: '\f23b';
+}
+.zmdi-format-align-right:before {
+ content: '\f23c';
+}
+.zmdi-format-bold:before {
+ content: '\f23d';
+}
+.zmdi-format-clear-all:before {
+ content: '\f23e';
+}
+.zmdi-format-clear:before {
+ content: '\f23f';
+}
+.zmdi-format-color-fill:before {
+ content: '\f240';
+}
+.zmdi-format-color-reset:before {
+ content: '\f241';
+}
+.zmdi-format-color-text:before {
+ content: '\f242';
+}
+.zmdi-format-indent-decrease:before {
+ content: '\f243';
+}
+.zmdi-format-indent-increase:before {
+ content: '\f244';
+}
+.zmdi-format-italic:before {
+ content: '\f245';
+}
+.zmdi-format-line-spacing:before {
+ content: '\f246';
+}
+.zmdi-format-list-bulleted:before {
+ content: '\f247';
+}
+.zmdi-format-list-numbered:before {
+ content: '\f248';
+}
+.zmdi-format-ltr:before {
+ content: '\f249';
+}
+.zmdi-format-rtl:before {
+ content: '\f24a';
+}
+.zmdi-format-size:before {
+ content: '\f24b';
+}
+.zmdi-format-strikethrough-s:before {
+ content: '\f24c';
+}
+.zmdi-format-strikethrough:before {
+ content: '\f24d';
+}
+.zmdi-format-subject:before {
+ content: '\f24e';
+}
+.zmdi-format-underlined:before {
+ content: '\f24f';
+}
+.zmdi-format-valign-bottom:before {
+ content: '\f250';
+}
+.zmdi-format-valign-center:before {
+ content: '\f251';
+}
+.zmdi-format-valign-top:before {
+ content: '\f252';
+}
+.zmdi-redo:before {
+ content: '\f253';
+}
+.zmdi-select-all:before {
+ content: '\f254';
+}
+.zmdi-space-bar:before {
+ content: '\f255';
+}
+.zmdi-text-format:before {
+ content: '\f256';
+}
+.zmdi-transform:before {
+ content: '\f257';
+}
+.zmdi-undo:before {
+ content: '\f258';
+}
+.zmdi-wrap-text:before {
+ content: '\f259';
+}
+.zmdi-comment-alert:before {
+ content: '\f25a';
+}
+.zmdi-comment-alt-text:before {
+ content: '\f25b';
+}
+.zmdi-comment-alt:before {
+ content: '\f25c';
+}
+.zmdi-comment-edit:before {
+ content: '\f25d';
+}
+.zmdi-comment-image:before {
+ content: '\f25e';
+}
+.zmdi-comment-list:before {
+ content: '\f25f';
+}
+.zmdi-comment-more:before {
+ content: '\f260';
+}
+.zmdi-comment-outline:before {
+ content: '\f261';
+}
+.zmdi-comment-text-alt:before {
+ content: '\f262';
+}
+.zmdi-comment-text:before {
+ content: '\f263';
+}
+.zmdi-comment-video:before {
+ content: '\f264';
+}
+.zmdi-comment:before {
+ content: '\f265';
+}
+.zmdi-comments:before {
+ content: '\f266';
+}
+.zmdi-check-all:before {
+ content: '\f267';
+}
+.zmdi-check-circle-u:before {
+ content: '\f268';
+}
+.zmdi-check-circle:before {
+ content: '\f269';
+}
+.zmdi-check-square:before {
+ content: '\f26a';
+}
+.zmdi-check:before {
+ content: '\f26b';
+}
+.zmdi-circle-o:before {
+ content: '\f26c';
+}
+.zmdi-circle:before {
+ content: '\f26d';
+}
+.zmdi-dot-circle-alt:before {
+ content: '\f26e';
+}
+.zmdi-dot-circle:before {
+ content: '\f26f';
+}
+.zmdi-minus-circle-outline:before {
+ content: '\f270';
+}
+.zmdi-minus-circle:before {
+ content: '\f271';
+}
+.zmdi-minus-square:before {
+ content: '\f272';
+}
+.zmdi-minus:before {
+ content: '\f273';
+}
+.zmdi-plus-circle-o-duplicate:before {
+ content: '\f274';
+}
+.zmdi-plus-circle-o:before {
+ content: '\f275';
+}
+.zmdi-plus-circle:before {
+ content: '\f276';
+}
+.zmdi-plus-square:before {
+ content: '\f277';
+}
+.zmdi-plus:before {
+ content: '\f278';
+}
+.zmdi-square-o:before {
+ content: '\f279';
+}
+.zmdi-star-circle:before {
+ content: '\f27a';
+}
+.zmdi-star-half:before {
+ content: '\f27b';
+}
+.zmdi-star-outline:before {
+ content: '\f27c';
+}
+.zmdi-star:before {
+ content: '\f27d';
+}
+.zmdi-bluetooth-connected:before {
+ content: '\f27e';
+}
+.zmdi-bluetooth-off:before {
+ content: '\f27f';
+}
+.zmdi-bluetooth-search:before {
+ content: '\f280';
+}
+.zmdi-bluetooth-setting:before {
+ content: '\f281';
+}
+.zmdi-bluetooth:before {
+ content: '\f282';
+}
+.zmdi-camera-add:before {
+ content: '\f283';
+}
+.zmdi-camera-alt:before {
+ content: '\f284';
+}
+.zmdi-camera-bw:before {
+ content: '\f285';
+}
+.zmdi-camera-front:before {
+ content: '\f286';
+}
+.zmdi-camera-mic:before {
+ content: '\f287';
+}
+.zmdi-camera-party-mode:before {
+ content: '\f288';
+}
+.zmdi-camera-rear:before {
+ content: '\f289';
+}
+.zmdi-camera-roll:before {
+ content: '\f28a';
+}
+.zmdi-camera-switch:before {
+ content: '\f28b';
+}
+.zmdi-camera:before {
+ content: '\f28c';
+}
+.zmdi-card-alert:before {
+ content: '\f28d';
+}
+.zmdi-card-off:before {
+ content: '\f28e';
+}
+.zmdi-card-sd:before {
+ content: '\f28f';
+}
+.zmdi-card-sim:before {
+ content: '\f290';
+}
+.zmdi-desktop-mac:before {
+ content: '\f291';
+}
+.zmdi-desktop-windows:before {
+ content: '\f292';
+}
+.zmdi-device-hub:before {
+ content: '\f293';
+}
+.zmdi-devices-off:before {
+ content: '\f294';
+}
+.zmdi-devices:before {
+ content: '\f295';
+}
+.zmdi-dock:before {
+ content: '\f296';
+}
+.zmdi-floppy:before {
+ content: '\f297';
+}
+.zmdi-gamepad:before {
+ content: '\f298';
+}
+.zmdi-gps-dot:before {
+ content: '\f299';
+}
+.zmdi-gps-off:before {
+ content: '\f29a';
+}
+.zmdi-gps:before {
+ content: '\f29b';
+}
+.zmdi-headset-mic:before {
+ content: '\f29c';
+}
+.zmdi-headset:before {
+ content: '\f29d';
+}
+.zmdi-input-antenna:before {
+ content: '\f29e';
+}
+.zmdi-input-composite:before {
+ content: '\f29f';
+}
+.zmdi-input-hdmi:before {
+ content: '\f2a0';
+}
+.zmdi-input-power:before {
+ content: '\f2a1';
+}
+.zmdi-input-svideo:before {
+ content: '\f2a2';
+}
+.zmdi-keyboard-hide:before {
+ content: '\f2a3';
+}
+.zmdi-keyboard:before {
+ content: '\f2a4';
+}
+.zmdi-laptop-chromebook:before {
+ content: '\f2a5';
+}
+.zmdi-laptop-mac:before {
+ content: '\f2a6';
+}
+.zmdi-laptop:before {
+ content: '\f2a7';
+}
+.zmdi-mic-off:before {
+ content: '\f2a8';
+}
+.zmdi-mic-outline:before {
+ content: '\f2a9';
+}
+.zmdi-mic-setting:before {
+ content: '\f2aa';
+}
+.zmdi-mic:before {
+ content: '\f2ab';
+}
+.zmdi-mouse:before {
+ content: '\f2ac';
+}
+.zmdi-network-alert:before {
+ content: '\f2ad';
+}
+.zmdi-network-locked:before {
+ content: '\f2ae';
+}
+.zmdi-network-off:before {
+ content: '\f2af';
+}
+.zmdi-network-outline:before {
+ content: '\f2b0';
+}
+.zmdi-network-setting:before {
+ content: '\f2b1';
+}
+.zmdi-network:before {
+ content: '\f2b2';
+}
+.zmdi-phone-bluetooth:before {
+ content: '\f2b3';
+}
+.zmdi-phone-end:before {
+ content: '\f2b4';
+}
+.zmdi-phone-forwarded:before {
+ content: '\f2b5';
+}
+.zmdi-phone-in-talk:before {
+ content: '\f2b6';
+}
+.zmdi-phone-locked:before {
+ content: '\f2b7';
+}
+.zmdi-phone-missed:before {
+ content: '\f2b8';
+}
+.zmdi-phone-msg:before {
+ content: '\f2b9';
+}
+.zmdi-phone-paused:before {
+ content: '\f2ba';
+}
+.zmdi-phone-ring:before {
+ content: '\f2bb';
+}
+.zmdi-phone-setting:before {
+ content: '\f2bc';
+}
+.zmdi-phone-sip:before {
+ content: '\f2bd';
+}
+.zmdi-phone:before {
+ content: '\f2be';
+}
+.zmdi-portable-wifi-changes:before {
+ content: '\f2bf';
+}
+.zmdi-portable-wifi-off:before {
+ content: '\f2c0';
+}
+.zmdi-portable-wifi:before {
+ content: '\f2c1';
+}
+.zmdi-radio:before {
+ content: '\f2c2';
+}
+.zmdi-reader:before {
+ content: '\f2c3';
+}
+.zmdi-remote-control-alt:before {
+ content: '\f2c4';
+}
+.zmdi-remote-control:before {
+ content: '\f2c5';
+}
+.zmdi-router:before {
+ content: '\f2c6';
+}
+.zmdi-scanner:before {
+ content: '\f2c7';
+}
+.zmdi-smartphone-android:before {
+ content: '\f2c8';
+}
+.zmdi-smartphone-download:before {
+ content: '\f2c9';
+}
+.zmdi-smartphone-erase:before {
+ content: '\f2ca';
+}
+.zmdi-smartphone-info:before {
+ content: '\f2cb';
+}
+.zmdi-smartphone-iphone:before {
+ content: '\f2cc';
+}
+.zmdi-smartphone-landscape-lock:before {
+ content: '\f2cd';
+}
+.zmdi-smartphone-landscape:before {
+ content: '\f2ce';
+}
+.zmdi-smartphone-lock:before {
+ content: '\f2cf';
+}
+.zmdi-smartphone-portrait-lock:before {
+ content: '\f2d0';
+}
+.zmdi-smartphone-ring:before {
+ content: '\f2d1';
+}
+.zmdi-smartphone-setting:before {
+ content: '\f2d2';
+}
+.zmdi-smartphone-setup:before {
+ content: '\f2d3';
+}
+.zmdi-smartphone:before {
+ content: '\f2d4';
+}
+.zmdi-speaker:before {
+ content: '\f2d5';
+}
+.zmdi-tablet-android:before {
+ content: '\f2d6';
+}
+.zmdi-tablet-mac:before {
+ content: '\f2d7';
+}
+.zmdi-tablet:before {
+ content: '\f2d8';
+}
+.zmdi-tv-alt-play:before {
+ content: '\f2d9';
+}
+.zmdi-tv-list:before {
+ content: '\f2da';
+}
+.zmdi-tv-play:before {
+ content: '\f2db';
+}
+.zmdi-tv:before {
+ content: '\f2dc';
+}
+.zmdi-usb:before {
+ content: '\f2dd';
+}
+.zmdi-videocam-off:before {
+ content: '\f2de';
+}
+.zmdi-videocam-switch:before {
+ content: '\f2df';
+}
+.zmdi-videocam:before {
+ content: '\f2e0';
+}
+.zmdi-watch:before {
+ content: '\f2e1';
+}
+.zmdi-wifi-alt-2:before {
+ content: '\f2e2';
+}
+.zmdi-wifi-alt:before {
+ content: '\f2e3';
+}
+.zmdi-wifi-info:before {
+ content: '\f2e4';
+}
+.zmdi-wifi-lock:before {
+ content: '\f2e5';
+}
+.zmdi-wifi-off:before {
+ content: '\f2e6';
+}
+.zmdi-wifi-outline:before {
+ content: '\f2e7';
+}
+.zmdi-wifi:before {
+ content: '\f2e8';
+}
+.zmdi-arrow-left-bottom:before {
+ content: '\f2e9';
+}
+.zmdi-arrow-left:before {
+ content: '\f2ea';
+}
+.zmdi-arrow-merge:before {
+ content: '\f2eb';
+}
+.zmdi-arrow-missed:before {
+ content: '\f2ec';
+}
+.zmdi-arrow-right-top:before {
+ content: '\f2ed';
+}
+.zmdi-arrow-right:before {
+ content: '\f2ee';
+}
+.zmdi-arrow-split:before {
+ content: '\f2ef';
+}
+.zmdi-arrows:before {
+ content: '\f2f0';
+}
+.zmdi-caret-down-circle:before {
+ content: '\f2f1';
+}
+.zmdi-caret-down:before {
+ content: '\f2f2';
+}
+.zmdi-caret-left-circle:before {
+ content: '\f2f3';
+}
+.zmdi-caret-left:before {
+ content: '\f2f4';
+}
+.zmdi-caret-right-circle:before {
+ content: '\f2f5';
+}
+.zmdi-caret-right:before {
+ content: '\f2f6';
+}
+.zmdi-caret-up-circle:before {
+ content: '\f2f7';
+}
+.zmdi-caret-up:before {
+ content: '\f2f8';
+}
+.zmdi-chevron-down:before {
+ content: '\f2f9';
+}
+.zmdi-chevron-left:before {
+ content: '\f2fa';
+}
+.zmdi-chevron-right:before {
+ content: '\f2fb';
+}
+.zmdi-chevron-up:before {
+ content: '\f2fc';
+}
+.zmdi-forward:before {
+ content: '\f2fd';
+}
+.zmdi-long-arrow-down:before {
+ content: '\f2fe';
+}
+.zmdi-long-arrow-left:before {
+ content: '\f2ff';
+}
+.zmdi-long-arrow-return:before {
+ content: '\f300';
+}
+.zmdi-long-arrow-right:before {
+ content: '\f301';
+}
+.zmdi-long-arrow-tab:before {
+ content: '\f302';
+}
+.zmdi-long-arrow-up:before {
+ content: '\f303';
+}
+.zmdi-rotate-ccw:before {
+ content: '\f304';
+}
+.zmdi-rotate-cw:before {
+ content: '\f305';
+}
+.zmdi-rotate-left:before {
+ content: '\f306';
+}
+.zmdi-rotate-right:before {
+ content: '\f307';
+}
+.zmdi-square-down:before {
+ content: '\f308';
+}
+.zmdi-square-right:before {
+ content: '\f309';
+}
+.zmdi-swap-alt:before {
+ content: '\f30a';
+}
+.zmdi-swap-vertical-circle:before {
+ content: '\f30b';
+}
+.zmdi-swap-vertical:before {
+ content: '\f30c';
+}
+.zmdi-swap:before {
+ content: '\f30d';
+}
+.zmdi-trending-down:before {
+ content: '\f30e';
+}
+.zmdi-trending-flat:before {
+ content: '\f30f';
+}
+.zmdi-trending-up:before {
+ content: '\f310';
+}
+.zmdi-unfold-less:before {
+ content: '\f311';
+}
+.zmdi-unfold-more:before {
+ content: '\f312';
+}
+.zmdi-apps:before {
+ content: '\f313';
+}
+.zmdi-grid-off:before {
+ content: '\f314';
+}
+.zmdi-grid:before {
+ content: '\f315';
+}
+.zmdi-view-agenda:before {
+ content: '\f316';
+}
+.zmdi-view-array:before {
+ content: '\f317';
+}
+.zmdi-view-carousel:before {
+ content: '\f318';
+}
+.zmdi-view-column:before {
+ content: '\f319';
+}
+.zmdi-view-comfy:before {
+ content: '\f31a';
+}
+.zmdi-view-compact:before {
+ content: '\f31b';
+}
+.zmdi-view-dashboard:before {
+ content: '\f31c';
+}
+.zmdi-view-day:before {
+ content: '\f31d';
+}
+.zmdi-view-headline:before {
+ content: '\f31e';
+}
+.zmdi-view-list-alt:before {
+ content: '\f31f';
+}
+.zmdi-view-list:before {
+ content: '\f320';
+}
+.zmdi-view-module:before {
+ content: '\f321';
+}
+.zmdi-view-quilt:before {
+ content: '\f322';
+}
+.zmdi-view-stream:before {
+ content: '\f323';
+}
+.zmdi-view-subtitles:before {
+ content: '\f324';
+}
+.zmdi-view-toc:before {
+ content: '\f325';
+}
+.zmdi-view-web:before {
+ content: '\f326';
+}
+.zmdi-view-week:before {
+ content: '\f327';
+}
+.zmdi-widgets:before {
+ content: '\f328';
+}
+.zmdi-alarm-check:before {
+ content: '\f329';
+}
+.zmdi-alarm-off:before {
+ content: '\f32a';
+}
+.zmdi-alarm-plus:before {
+ content: '\f32b';
+}
+.zmdi-alarm-snooze:before {
+ content: '\f32c';
+}
+.zmdi-alarm:before {
+ content: '\f32d';
+}
+.zmdi-calendar-alt:before {
+ content: '\f32e';
+}
+.zmdi-calendar-check:before {
+ content: '\f32f';
+}
+.zmdi-calendar-close:before {
+ content: '\f330';
+}
+.zmdi-calendar-note:before {
+ content: '\f331';
+}
+.zmdi-calendar:before {
+ content: '\f332';
+}
+.zmdi-time-countdown:before {
+ content: '\f333';
+}
+.zmdi-time-interval:before {
+ content: '\f334';
+}
+.zmdi-time-restore-setting:before {
+ content: '\f335';
+}
+.zmdi-time-restore:before {
+ content: '\f336';
+}
+.zmdi-time:before {
+ content: '\f337';
+}
+.zmdi-timer-off:before {
+ content: '\f338';
+}
+.zmdi-timer:before {
+ content: '\f339';
+}
+.zmdi-android-alt:before {
+ content: '\f33a';
+}
+.zmdi-android:before {
+ content: '\f33b';
+}
+.zmdi-apple:before {
+ content: '\f33c';
+}
+.zmdi-behance:before {
+ content: '\f33d';
+}
+.zmdi-codepen:before {
+ content: '\f33e';
+}
+.zmdi-dribbble:before {
+ content: '\f33f';
+}
+.zmdi-dropbox:before {
+ content: '\f340';
+}
+.zmdi-evernote:before {
+ content: '\f341';
+}
+.zmdi-facebook-box:before {
+ content: '\f342';
+}
+.zmdi-facebook:before {
+ content: '\f343';
+}
+.zmdi-github-box:before {
+ content: '\f344';
+}
+.zmdi-github:before {
+ content: '\f345';
+}
+.zmdi-google-drive:before {
+ content: '\f346';
+}
+.zmdi-google-earth:before {
+ content: '\f347';
+}
+.zmdi-google-glass:before {
+ content: '\f348';
+}
+.zmdi-google-maps:before {
+ content: '\f349';
+}
+.zmdi-google-pages:before {
+ content: '\f34a';
+}
+.zmdi-google-play:before {
+ content: '\f34b';
+}
+.zmdi-google-plus-box:before {
+ content: '\f34c';
+}
+.zmdi-google-plus:before {
+ content: '\f34d';
+}
+.zmdi-google:before {
+ content: '\f34e';
+}
+.zmdi-instagram:before {
+ content: '\f34f';
+}
+.zmdi-language-css3:before {
+ content: '\f350';
+}
+.zmdi-language-html5:before {
+ content: '\f351';
+}
+.zmdi-language-javascript:before {
+ content: '\f352';
+}
+.zmdi-language-python-alt:before {
+ content: '\f353';
+}
+.zmdi-language-python:before {
+ content: '\f354';
+}
+.zmdi-lastfm:before {
+ content: '\f355';
+}
+.zmdi-linkedin-box:before {
+ content: '\f356';
+}
+.zmdi-paypal:before {
+ content: '\f357';
+}
+.zmdi-pinterest-box:before {
+ content: '\f358';
+}
+.zmdi-pocket:before {
+ content: '\f359';
+}
+.zmdi-polymer:before {
+ content: '\f35a';
+}
+.zmdi-share:before {
+ content: '\f35b';
+}
+.zmdi-stackoverflow:before {
+ content: '\f35c';
+}
+.zmdi-steam-square:before {
+ content: '\f35d';
+}
+.zmdi-steam:before {
+ content: '\f35e';
+}
+.zmdi-twitter-box:before {
+ content: '\f35f';
+}
+.zmdi-twitter:before {
+ content: '\f360';
+}
+.zmdi-vk:before {
+ content: '\f361';
+}
+.zmdi-wikipedia:before {
+ content: '\f362';
+}
+.zmdi-windows:before {
+ content: '\f363';
+}
+.zmdi-aspect-ratio-alt:before {
+ content: '\f364';
+}
+.zmdi-aspect-ratio:before {
+ content: '\f365';
+}
+.zmdi-blur-circular:before {
+ content: '\f366';
+}
+.zmdi-blur-linear:before {
+ content: '\f367';
+}
+.zmdi-blur-off:before {
+ content: '\f368';
+}
+.zmdi-blur:before {
+ content: '\f369';
+}
+.zmdi-brightness-2:before {
+ content: '\f36a';
+}
+.zmdi-brightness-3:before {
+ content: '\f36b';
+}
+.zmdi-brightness-4:before {
+ content: '\f36c';
+}
+.zmdi-brightness-5:before {
+ content: '\f36d';
+}
+.zmdi-brightness-6:before {
+ content: '\f36e';
+}
+.zmdi-brightness-7:before {
+ content: '\f36f';
+}
+.zmdi-brightness-auto:before {
+ content: '\f370';
+}
+.zmdi-brightness-setting:before {
+ content: '\f371';
+}
+.zmdi-broken-image:before {
+ content: '\f372';
+}
+.zmdi-center-focus-strong:before {
+ content: '\f373';
+}
+.zmdi-center-focus-weak:before {
+ content: '\f374';
+}
+.zmdi-compare:before {
+ content: '\f375';
+}
+.zmdi-crop-16-9:before {
+ content: '\f376';
+}
+.zmdi-crop-3-2:before {
+ content: '\f377';
+}
+.zmdi-crop-5-4:before {
+ content: '\f378';
+}
+.zmdi-crop-7-5:before {
+ content: '\f379';
+}
+.zmdi-crop-din:before {
+ content: '\f37a';
+}
+.zmdi-crop-free:before {
+ content: '\f37b';
+}
+.zmdi-crop-landscape:before {
+ content: '\f37c';
+}
+.zmdi-crop-portrait:before {
+ content: '\f37d';
+}
+.zmdi-crop-square:before {
+ content: '\f37e';
+}
+.zmdi-exposure-alt:before {
+ content: '\f37f';
+}
+.zmdi-exposure:before {
+ content: '\f380';
+}
+.zmdi-filter-b-and-w:before {
+ content: '\f381';
+}
+.zmdi-filter-center-focus:before {
+ content: '\f382';
+}
+.zmdi-filter-frames:before {
+ content: '\f383';
+}
+.zmdi-filter-tilt-shift:before {
+ content: '\f384';
+}
+.zmdi-gradient:before {
+ content: '\f385';
+}
+.zmdi-grain:before {
+ content: '\f386';
+}
+.zmdi-graphic-eq:before {
+ content: '\f387';
+}
+.zmdi-hdr-off:before {
+ content: '\f388';
+}
+.zmdi-hdr-strong:before {
+ content: '\f389';
+}
+.zmdi-hdr-weak:before {
+ content: '\f38a';
+}
+.zmdi-hdr:before {
+ content: '\f38b';
+}
+.zmdi-iridescent:before {
+ content: '\f38c';
+}
+.zmdi-leak-off:before {
+ content: '\f38d';
+}
+.zmdi-leak:before {
+ content: '\f38e';
+}
+.zmdi-looks:before {
+ content: '\f38f';
+}
+.zmdi-loupe:before {
+ content: '\f390';
+}
+.zmdi-panorama-horizontal:before {
+ content: '\f391';
+}
+.zmdi-panorama-vertical:before {
+ content: '\f392';
+}
+.zmdi-panorama-wide-angle:before {
+ content: '\f393';
+}
+.zmdi-photo-size-select-large:before {
+ content: '\f394';
+}
+.zmdi-photo-size-select-small:before {
+ content: '\f395';
+}
+.zmdi-picture-in-picture:before {
+ content: '\f396';
+}
+.zmdi-slideshow:before {
+ content: '\f397';
+}
+.zmdi-texture:before {
+ content: '\f398';
+}
+.zmdi-tonality:before {
+ content: '\f399';
+}
+.zmdi-vignette:before {
+ content: '\f39a';
+}
+.zmdi-wb-auto:before {
+ content: '\f39b';
+}
+.zmdi-eject-alt:before {
+ content: '\f39c';
+}
+.zmdi-eject:before {
+ content: '\f39d';
+}
+.zmdi-equalizer:before {
+ content: '\f39e';
+}
+.zmdi-fast-forward:before {
+ content: '\f39f';
+}
+.zmdi-fast-rewind:before {
+ content: '\f3a0';
+}
+.zmdi-forward-10:before {
+ content: '\f3a1';
+}
+.zmdi-forward-30:before {
+ content: '\f3a2';
+}
+.zmdi-forward-5:before {
+ content: '\f3a3';
+}
+.zmdi-hearing:before {
+ content: '\f3a4';
+}
+.zmdi-pause-circle-outline:before {
+ content: '\f3a5';
+}
+.zmdi-pause-circle:before {
+ content: '\f3a6';
+}
+.zmdi-pause:before {
+ content: '\f3a7';
+}
+.zmdi-play-circle-outline:before {
+ content: '\f3a8';
+}
+.zmdi-play-circle:before {
+ content: '\f3a9';
+}
+.zmdi-play:before {
+ content: '\f3aa';
+}
+.zmdi-playlist-audio:before {
+ content: '\f3ab';
+}
+.zmdi-playlist-plus:before {
+ content: '\f3ac';
+}
+.zmdi-repeat-one:before {
+ content: '\f3ad';
+}
+.zmdi-repeat:before {
+ content: '\f3ae';
+}
+.zmdi-replay-10:before {
+ content: '\f3af';
+}
+.zmdi-replay-30:before {
+ content: '\f3b0';
+}
+.zmdi-replay-5:before {
+ content: '\f3b1';
+}
+.zmdi-replay:before {
+ content: '\f3b2';
+}
+.zmdi-shuffle:before {
+ content: '\f3b3';
+}
+.zmdi-skip-next:before {
+ content: '\f3b4';
+}
+.zmdi-skip-previous:before {
+ content: '\f3b5';
+}
+.zmdi-stop:before {
+ content: '\f3b6';
+}
+.zmdi-surround-sound:before {
+ content: '\f3b7';
+}
+.zmdi-tune:before {
+ content: '\f3b8';
+}
+.zmdi-volume-down:before {
+ content: '\f3b9';
+}
+.zmdi-volume-mute:before {
+ content: '\f3ba';
+}
+.zmdi-volume-off:before {
+ content: '\f3bb';
+}
+.zmdi-volume-up:before {
+ content: '\f3bc';
+}
+.zmdi-n-1-square:before {
+ content: '\f3bd';
+}
+.zmdi-n-2-square:before {
+ content: '\f3be';
+}
+.zmdi-n-3-square:before {
+ content: '\f3bf';
+}
+.zmdi-n-4-square:before {
+ content: '\f3c0';
+}
+.zmdi-n-5-square:before {
+ content: '\f3c1';
+}
+.zmdi-n-6-square:before {
+ content: '\f3c2';
+}
+.zmdi-neg-1:before {
+ content: '\f3c3';
+}
+.zmdi-neg-2:before {
+ content: '\f3c4';
+}
+.zmdi-plus-1:before {
+ content: '\f3c5';
+}
+.zmdi-plus-2:before {
+ content: '\f3c6';
+}
+.zmdi-sec-10:before {
+ content: '\f3c7';
+}
+.zmdi-sec-3:before {
+ content: '\f3c8';
+}
+.zmdi-zero:before {
+ content: '\f3c9';
+}
+.zmdi-airline-seat-flat-angled:before {
+ content: '\f3ca';
+}
+.zmdi-airline-seat-flat:before {
+ content: '\f3cb';
+}
+.zmdi-airline-seat-individual-suite:before {
+ content: '\f3cc';
+}
+.zmdi-airline-seat-legroom-extra:before {
+ content: '\f3cd';
+}
+.zmdi-airline-seat-legroom-normal:before {
+ content: '\f3ce';
+}
+.zmdi-airline-seat-legroom-reduced:before {
+ content: '\f3cf';
+}
+.zmdi-airline-seat-recline-extra:before {
+ content: '\f3d0';
+}
+.zmdi-airline-seat-recline-normal:before {
+ content: '\f3d1';
+}
+.zmdi-airplay:before {
+ content: '\f3d2';
+}
+.zmdi-closed-caption:before {
+ content: '\f3d3';
+}
+.zmdi-confirmation-number:before {
+ content: '\f3d4';
+}
+.zmdi-developer-board:before {
+ content: '\f3d5';
+}
+.zmdi-disc-full:before {
+ content: '\f3d6';
+}
+.zmdi-explicit:before {
+ content: '\f3d7';
+}
+.zmdi-flight-land:before {
+ content: '\f3d8';
+}
+.zmdi-flight-takeoff:before {
+ content: '\f3d9';
+}
+.zmdi-flip-to-back:before {
+ content: '\f3da';
+}
+.zmdi-flip-to-front:before {
+ content: '\f3db';
+}
+.zmdi-group-work:before {
+ content: '\f3dc';
+}
+.zmdi-hd:before {
+ content: '\f3dd';
+}
+.zmdi-hq:before {
+ content: '\f3de';
+}
+.zmdi-markunread-mailbox:before {
+ content: '\f3df';
+}
+.zmdi-memory:before {
+ content: '\f3e0';
+}
+.zmdi-nfc:before {
+ content: '\f3e1';
+}
+.zmdi-play-for-work:before {
+ content: '\f3e2';
+}
+.zmdi-power-input:before {
+ content: '\f3e3';
+}
+.zmdi-present-to-all:before {
+ content: '\f3e4';
+}
+.zmdi-satellite:before {
+ content: '\f3e5';
+}
+.zmdi-tap-and-play:before {
+ content: '\f3e6';
+}
+.zmdi-vibration:before {
+ content: '\f3e7';
+}
+.zmdi-voicemail:before {
+ content: '\f3e8';
+}
+.zmdi-group:before {
+ content: '\f3e9';
+}
+.zmdi-rss:before {
+ content: '\f3ea';
+}
+.zmdi-shape:before {
+ content: '\f3eb';
+}
+.zmdi-spinner:before {
+ content: '\f3ec';
+}
+.zmdi-ungroup:before {
+ content: '\f3ed';
+}
+.zmdi-500px:before {
+ content: '\f3ee';
+}
+.zmdi-8tracks:before {
+ content: '\f3ef';
+}
+.zmdi-amazon:before {
+ content: '\f3f0';
+}
+.zmdi-blogger:before {
+ content: '\f3f1';
+}
+.zmdi-delicious:before {
+ content: '\f3f2';
+}
+.zmdi-disqus:before {
+ content: '\f3f3';
+}
+.zmdi-flattr:before {
+ content: '\f3f4';
+}
+.zmdi-flickr:before {
+ content: '\f3f5';
+}
+.zmdi-github-alt:before {
+ content: '\f3f6';
+}
+.zmdi-google-old:before {
+ content: '\f3f7';
+}
+.zmdi-linkedin:before {
+ content: '\f3f8';
+}
+.zmdi-odnoklassniki:before {
+ content: '\f3f9';
+}
+.zmdi-outlook:before {
+ content: '\f3fa';
+}
+.zmdi-paypal-alt:before {
+ content: '\f3fb';
+}
+.zmdi-pinterest:before {
+ content: '\f3fc';
+}
+.zmdi-playstation:before {
+ content: '\f3fd';
+}
+.zmdi-reddit:before {
+ content: '\f3fe';
+}
+.zmdi-skype:before {
+ content: '\f3ff';
+}
+.zmdi-slideshare:before {
+ content: '\f400';
+}
+.zmdi-soundcloud:before {
+ content: '\f401';
+}
+.zmdi-tumblr:before {
+ content: '\f402';
+}
+.zmdi-twitch:before {
+ content: '\f403';
+}
+.zmdi-vimeo:before {
+ content: '\f404';
+}
+.zmdi-whatsapp:before {
+ content: '\f405';
+}
+.zmdi-xbox:before {
+ content: '\f406';
+}
+.zmdi-yahoo:before {
+ content: '\f407';
+}
+.zmdi-youtube-play:before {
+ content: '\f408';
+}
+.zmdi-youtube:before {
+ content: '\f409';
+}
+.zmdi-import-export:before {
+ content: '\f30c';
+}
+.zmdi-swap-vertical-:before {
+ content: '\f30c';
+}
+.zmdi-airplanemode-inactive:before {
+ content: '\f102';
+}
+.zmdi-airplanemode-active:before {
+ content: '\f103';
+}
+.zmdi-rate-review:before {
+ content: '\f103';
+}
+.zmdi-comment-sign:before {
+ content: '\f25a';
+}
+.zmdi-network-warning:before {
+ content: '\f2ad';
+}
+.zmdi-shopping-cart-add:before {
+ content: '\f1ca';
+}
+.zmdi-file-add:before {
+ content: '\f221';
+}
+.zmdi-network-wifi-scan:before {
+ content: '\f2e4';
+}
+.zmdi-collection-add:before {
+ content: '\f14e';
+}
+.zmdi-format-playlist-add:before {
+ content: '\f3ac';
+}
+.zmdi-format-queue-music:before {
+ content: '\f3ab';
+}
+.zmdi-plus-box:before {
+ content: '\f277';
+}
+.zmdi-tag-backspace:before {
+ content: '\f1d9';
+}
+.zmdi-alarm-add:before {
+ content: '\f32b';
+}
+.zmdi-battery-charging:before {
+ content: '\f114';
+}
+.zmdi-daydream-setting:before {
+ content: '\f217';
+}
+.zmdi-more-horiz:before {
+ content: '\f19c';
+}
+.zmdi-book-photo:before {
+ content: '\f11b';
+}
+.zmdi-incandescent:before {
+ content: '\f189';
+}
+.zmdi-wb-iridescent:before {
+ content: '\f38c';
+}
+.zmdi-calendar-remove:before {
+ content: '\f330';
+}
+.zmdi-refresh-sync-disabled:before {
+ content: '\f1b7';
+}
+.zmdi-refresh-sync-problem:before {
+ content: '\f1b6';
+}
+.zmdi-crop-original:before {
+ content: '\f17e';
+}
+.zmdi-power-off:before {
+ content: '\f1af';
+}
+.zmdi-power-off-setting:before {
+ content: '\f1ae';
+}
+.zmdi-leak-remove:before {
+ content: '\f38d';
+}
+.zmdi-star-border:before {
+ content: '\f27c';
+}
+.zmdi-brightness-low:before {
+ content: '\f36d';
+}
+.zmdi-brightness-medium:before {
+ content: '\f36e';
+}
+.zmdi-brightness-high:before {
+ content: '\f36f';
+}
+.zmdi-smartphone-portrait:before {
+ content: '\f2d4';
+}
+.zmdi-live-tv:before {
+ content: '\f2d9';
+}
+.zmdi-format-textdirection-l-to-r:before {
+ content: '\f249';
+}
+.zmdi-format-textdirection-r-to-l:before {
+ content: '\f24a';
+}
+.zmdi-arrow-back:before {
+ content: '\f2ea';
+}
+.zmdi-arrow-forward:before {
+ content: '\f2ee';
+}
+.zmdi-arrow-in:before {
+ content: '\f2e9';
+}
+.zmdi-arrow-out:before {
+ content: '\f2ed';
+}
+.zmdi-rotate-90-degrees-ccw:before {
+ content: '\f304';
+}
+.zmdi-adb:before {
+ content: '\f33a';
+}
+.zmdi-network-wifi:before {
+ content: '\f2e8';
+}
+.zmdi-network-wifi-alt:before {
+ content: '\f2e3';
+}
+.zmdi-network-wifi-lock:before {
+ content: '\f2e5';
+}
+.zmdi-network-wifi-off:before {
+ content: '\f2e6';
+}
+.zmdi-network-wifi-outline:before {
+ content: '\f2e7';
+}
+.zmdi-network-wifi-info:before {
+ content: '\f2e4';
+}
+.zmdi-layers-clear:before {
+ content: '\f18b';
+}
+.zmdi-colorize:before {
+ content: '\f15d';
+}
+.zmdi-format-paint:before {
+ content: '\f1ba';
+}
+.zmdi-format-quote:before {
+ content: '\f1b2';
+}
+.zmdi-camera-monochrome-photos:before {
+ content: '\f285';
+}
+.zmdi-sort-by-alpha:before {
+ content: '\f1cf';
+}
+.zmdi-folder-shared:before {
+ content: '\f225';
+}
+.zmdi-folder-special:before {
+ content: '\f226';
+}
+.zmdi-comment-dots:before {
+ content: '\f260';
+}
+.zmdi-reorder:before {
+ content: '\f31e';
+}
+.zmdi-dehaze:before {
+ content: '\f197';
+}
+.zmdi-sort:before {
+ content: '\f1ce';
+}
+.zmdi-pages:before {
+ content: '\f34a';
+}
+.zmdi-stack-overflow:before {
+ content: '\f35c';
+}
+.zmdi-calendar-account:before {
+ content: '\f204';
+}
+.zmdi-paste:before {
+ content: '\f109';
+}
+.zmdi-cut:before {
+ content: '\f1bc';
+}
+.zmdi-save:before {
+ content: '\f297';
+}
+.zmdi-smartphone-code:before {
+ content: '\f139';
+}
+.zmdi-directions-bike:before {
+ content: '\f117';
+}
+.zmdi-directions-boat:before {
+ content: '\f11a';
+}
+.zmdi-directions-bus:before {
+ content: '\f121';
+}
+.zmdi-directions-car:before {
+ content: '\f125';
+}
+.zmdi-directions-railway:before {
+ content: '\f1b3';
+}
+.zmdi-directions-run:before {
+ content: '\f215';
+}
+.zmdi-directions-subway:before {
+ content: '\f1d5';
+}
+.zmdi-directions-walk:before {
+ content: '\f216';
+}
+.zmdi-local-hotel:before {
+ content: '\f178';
+}
+.zmdi-local-activity:before {
+ content: '\f1df';
+}
+.zmdi-local-play:before {
+ content: '\f1df';
+}
+.zmdi-local-airport:before {
+ content: '\f103';
+}
+.zmdi-local-atm:before {
+ content: '\f198';
+}
+.zmdi-local-bar:before {
+ content: '\f137';
+}
+.zmdi-local-cafe:before {
+ content: '\f13b';
+}
+.zmdi-local-car-wash:before {
+ content: '\f124';
+}
+.zmdi-local-convenience-store:before {
+ content: '\f1d3';
+}
+.zmdi-local-dining:before {
+ content: '\f153';
+}
+.zmdi-local-drink:before {
+ content: '\f157';
+}
+.zmdi-local-florist:before {
+ content: '\f168';
+}
+.zmdi-local-gas-station:before {
+ content: '\f16f';
+}
+.zmdi-local-grocery-store:before {
+ content: '\f1cb';
+}
+.zmdi-local-hospital:before {
+ content: '\f177';
+}
+.zmdi-local-laundry-service:before {
+ content: '\f1e9';
+}
+.zmdi-local-library:before {
+ content: '\f18d';
+}
+.zmdi-local-mall:before {
+ content: '\f195';
+}
+.zmdi-local-movies:before {
+ content: '\f19d';
+}
+.zmdi-local-offer:before {
+ content: '\f187';
+}
+.zmdi-local-parking:before {
+ content: '\f1a5';
+}
+.zmdi-local-parking:before {
+ content: '\f1a5';
+}
+.zmdi-local-pharmacy:before {
+ content: '\f176';
+}
+.zmdi-local-phone:before {
+ content: '\f2be';
+}
+.zmdi-local-pizza:before {
+ content: '\f1ac';
+}
+.zmdi-local-post-office:before {
+ content: '\f15a';
+}
+.zmdi-local-printshop:before {
+ content: '\f1b0';
+}
+.zmdi-local-see:before {
+ content: '\f28c';
+}
+.zmdi-local-shipping:before {
+ content: '\f1e6';
+}
+.zmdi-local-store:before {
+ content: '\f1d4';
+}
+.zmdi-local-taxi:before {
+ content: '\f123';
+}
+.zmdi-local-wc:before {
+ content: '\f211';
+}
+.zmdi-my-location:before {
+ content: '\f299';
+}
+.zmdi-directions:before {
+ content: '\f1e7';
+}
diff --git a/packages/website/public/css/material-design-iconic-font.min.css b/packages/website/public/css/material-design-iconic-font.min.css
new file mode 100755
index 000000000..e1a58fe2f
--- /dev/null
+++ b/packages/website/public/css/material-design-iconic-font.min.css
@@ -0,0 +1 @@
+@font-face{font-family:Material-Design-Iconic-Font;src:url(../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0) format('woff2'),url(../fonts/Material-Design-Iconic-Font.woff?v=2.2.0) format('woff'),url(../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0) format('truetype')}.zmdi{display:inline-block;font:normal normal normal 14px/1 'Material-Design-Iconic-Font';font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.zmdi-hc-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.zmdi-hc-2x{font-size:2em}.zmdi-hc-3x{font-size:3em}.zmdi-hc-4x{font-size:4em}.zmdi-hc-5x{font-size:5em}.zmdi-hc-fw{width:1.28571429em;text-align:center}.zmdi-hc-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.zmdi-hc-ul>li{position:relative}.zmdi-hc-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.zmdi-hc-li.zmdi-hc-lg{left:-1.85714286em}.zmdi-hc-border{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:2px}.zmdi-hc-border-circle{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:50%}.zmdi.pull-left{float:left;margin-right:.15em}.zmdi.pull-right{float:right;margin-left:.15em}.zmdi-hc-spin{-webkit-animation:zmdi-spin 1.5s infinite linear;animation:zmdi-spin 1.5s infinite linear}.zmdi-hc-spin-reverse{-webkit-animation:zmdi-spin-reverse 1.5s infinite linear;animation:zmdi-spin-reverse 1.5s infinite linear}@-webkit-keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}@keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}.zmdi-hc-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.zmdi-hc-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.zmdi-hc-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.zmdi-hc-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.zmdi-hc-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}.zmdi-hc-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.zmdi-hc-stack-1x,.zmdi-hc-stack-2x{position:absolute;left:0;width:100%;text-align:center}.zmdi-hc-stack-1x{line-height:inherit}.zmdi-hc-stack-2x{font-size:2em}.zmdi-hc-inverse{color:#fff}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-import-export:before{content:'\f30c'}.zmdi-swap-vertical-:before{content:'\f30c'}.zmdi-airplanemode-inactive:before{content:'\f102'}.zmdi-airplanemode-active:before{content:'\f103'}.zmdi-rate-review:before{content:'\f103'}.zmdi-comment-sign:before{content:'\f25a'}.zmdi-network-warning:before{content:'\f2ad'}.zmdi-shopping-cart-add:before{content:'\f1ca'}.zmdi-file-add:before{content:'\f221'}.zmdi-network-wifi-scan:before{content:'\f2e4'}.zmdi-collection-add:before{content:'\f14e'}.zmdi-format-playlist-add:before{content:'\f3ac'}.zmdi-format-queue-music:before{content:'\f3ab'}.zmdi-plus-box:before{content:'\f277'}.zmdi-tag-backspace:before{content:'\f1d9'}.zmdi-alarm-add:before{content:'\f32b'}.zmdi-battery-charging:before{content:'\f114'}.zmdi-daydream-setting:before{content:'\f217'}.zmdi-more-horiz:before{content:'\f19c'}.zmdi-book-photo:before{content:'\f11b'}.zmdi-incandescent:before{content:'\f189'}.zmdi-wb-iridescent:before{content:'\f38c'}.zmdi-calendar-remove:before{content:'\f330'}.zmdi-refresh-sync-disabled:before{content:'\f1b7'}.zmdi-refresh-sync-problem:before{content:'\f1b6'}.zmdi-crop-original:before{content:'\f17e'}.zmdi-power-off:before{content:'\f1af'}.zmdi-power-off-setting:before{content:'\f1ae'}.zmdi-leak-remove:before{content:'\f38d'}.zmdi-star-border:before{content:'\f27c'}.zmdi-brightness-low:before{content:'\f36d'}.zmdi-brightness-medium:before{content:'\f36e'}.zmdi-brightness-high:before{content:'\f36f'}.zmdi-smartphone-portrait:before{content:'\f2d4'}.zmdi-live-tv:before{content:'\f2d9'}.zmdi-format-textdirection-l-to-r:before{content:'\f249'}.zmdi-format-textdirection-r-to-l:before{content:'\f24a'}.zmdi-arrow-back:before{content:'\f2ea'}.zmdi-arrow-forward:before{content:'\f2ee'}.zmdi-arrow-in:before{content:'\f2e9'}.zmdi-arrow-out:before{content:'\f2ed'}.zmdi-rotate-90-degrees-ccw:before{content:'\f304'}.zmdi-adb:before{content:'\f33a'}.zmdi-network-wifi:before{content:'\f2e8'}.zmdi-network-wifi-alt:before{content:'\f2e3'}.zmdi-network-wifi-lock:before{content:'\f2e5'}.zmdi-network-wifi-off:before{content:'\f2e6'}.zmdi-network-wifi-outline:before{content:'\f2e7'}.zmdi-network-wifi-info:before{content:'\f2e4'}.zmdi-layers-clear:before{content:'\f18b'}.zmdi-colorize:before{content:'\f15d'}.zmdi-format-paint:before{content:'\f1ba'}.zmdi-format-quote:before{content:'\f1b2'}.zmdi-camera-monochrome-photos:before{content:'\f285'}.zmdi-sort-by-alpha:before{content:'\f1cf'}.zmdi-folder-shared:before{content:'\f225'}.zmdi-folder-special:before{content:'\f226'}.zmdi-comment-dots:before{content:'\f260'}.zmdi-reorder:before{content:'\f31e'}.zmdi-dehaze:before{content:'\f197'}.zmdi-sort:before{content:'\f1ce'}.zmdi-pages:before{content:'\f34a'}.zmdi-stack-overflow:before{content:'\f35c'}.zmdi-calendar-account:before{content:'\f204'}.zmdi-paste:before{content:'\f109'}.zmdi-cut:before{content:'\f1bc'}.zmdi-save:before{content:'\f297'}.zmdi-smartphone-code:before{content:'\f139'}.zmdi-directions-bike:before{content:'\f117'}.zmdi-directions-boat:before{content:'\f11a'}.zmdi-directions-bus:before{content:'\f121'}.zmdi-directions-car:before{content:'\f125'}.zmdi-directions-railway:before{content:'\f1b3'}.zmdi-directions-run:before{content:'\f215'}.zmdi-directions-subway:before{content:'\f1d5'}.zmdi-directions-walk:before{content:'\f216'}.zmdi-local-hotel:before{content:'\f178'}.zmdi-local-activity:before{content:'\f1df'}.zmdi-local-play:before{content:'\f1df'}.zmdi-local-airport:before{content:'\f103'}.zmdi-local-atm:before{content:'\f198'}.zmdi-local-bar:before{content:'\f137'}.zmdi-local-cafe:before{content:'\f13b'}.zmdi-local-car-wash:before{content:'\f124'}.zmdi-local-convenience-store:before{content:'\f1d3'}.zmdi-local-dining:before{content:'\f153'}.zmdi-local-drink:before{content:'\f157'}.zmdi-local-florist:before{content:'\f168'}.zmdi-local-gas-station:before{content:'\f16f'}.zmdi-local-grocery-store:before{content:'\f1cb'}.zmdi-local-hospital:before{content:'\f177'}.zmdi-local-laundry-service:before{content:'\f1e9'}.zmdi-local-library:before{content:'\f18d'}.zmdi-local-mall:before{content:'\f195'}.zmdi-local-movies:before{content:'\f19d'}.zmdi-local-offer:before{content:'\f187'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-pharmacy:before{content:'\f176'}.zmdi-local-phone:before{content:'\f2be'}.zmdi-local-pizza:before{content:'\f1ac'}.zmdi-local-post-office:before{content:'\f15a'}.zmdi-local-printshop:before{content:'\f1b0'}.zmdi-local-see:before{content:'\f28c'}.zmdi-local-shipping:before{content:'\f1e6'}.zmdi-local-store:before{content:'\f1d4'}.zmdi-local-taxi:before{content:'\f123'}.zmdi-local-wc:before{content:'\f211'}.zmdi-my-location:before{content:'\f299'}.zmdi-directions:before{content:'\f1e7'} \ No newline at end of file
diff --git a/packages/website/public/css/roboto.css b/packages/website/public/css/roboto.css
new file mode 100644
index 000000000..7af568a74
--- /dev/null
+++ b/packages/website/public/css/roboto.css
@@ -0,0 +1,83 @@
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Thin.ttf') format('truetype');
+ font-weight: 100;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype');
+ font-weight: 100;
+ font-style: italic;
+}*/
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Light.ttf') format('truetype');
+ font-weight: 300;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-LightItalic.ttf') format('truetype');
+ font-weight: 300;
+ font-style: italic;
+}*/
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-RegularItalic.ttf') format('truetype');
+ font-weight: 400;
+ font-style: italic;
+}*/
+
+/*@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Medium.ttf') format('truetype');
+ font-weight: 500;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype');
+ font-weight: 500;
+ font-style: italic;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Bold.ttf') format('truetype');
+ font-weight: 700;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype');
+ font-weight: 700;
+ font-style: italic;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Black.ttf') format('truetype');
+ font-weight: 900;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype');
+ font-weight: 900;
+ font-style: italic;
+}*/
diff --git a/packages/website/public/css/roboto_mono.css b/packages/website/public/css/roboto_mono.css
new file mode 100644
index 000000000..f8159d35f
--- /dev/null
+++ b/packages/website/public/css/roboto_mono.css
@@ -0,0 +1,69 @@
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-Thin.ttf') format('truetype');
+ font-weight: 100;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-ThinItalic.ttf') format('truetype');
+ font-weight: 100;
+ font-style: italic;
+}*/
+
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-Light.ttf') format('truetype');
+ font-weight: 300;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-LightItalic.ttf') format('truetype');
+ font-weight: 300;
+ font-style: italic;
+}*/
+
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+/*@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-RegularItalic.ttf') format('truetype');
+ font-weight: 400;
+ font-style: italic;
+}*/
+
+/*@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-Medium.ttf') format('truetype');
+ font-weight: 500;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-MediumItalic.ttf') format('truetype');
+ font-weight: 500;
+ font-style: italic;
+}
+
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-Bold.ttf') format('truetype');
+ font-weight: 700;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto Mono';
+ src: url('../fonts/RobotoMono-BoldItalic.ttf') format('truetype');
+ font-weight: 700;
+ font-style: italic;
+}*/
diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.eot b/packages/website/public/fonts/Material-Design-Iconic-Font.eot
new file mode 100755
index 000000000..5e2519150
--- /dev/null
+++ b/packages/website/public/fonts/Material-Design-Iconic-Font.eot
Binary files differ
diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.svg b/packages/website/public/fonts/Material-Design-Iconic-Font.svg
new file mode 100755
index 000000000..1d3d2eaa2
--- /dev/null
+++ b/packages/website/public/fonts/Material-Design-Iconic-Font.svg
@@ -0,0 +1,787 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="material-desidesigniconicfont" horiz-adv-x="427" >
+<font-face units-per-em="512" ascent="448" descent="-64" />
+<missing-glyph horiz-adv-x="500" />
+<glyph unicode="&#xf101;" horiz-adv-x="510" d="M159 -10l29 28l81 -81l-14 -1q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73zM178 129q14 0 21 7t7 20q0 7 -2 12t-6 8q-4 4 -9.5 5.5t-13.5 1.5h-16v22h16q8 0 13 2t8 5q4 3 6 8t2 10q0 12 -7 19q-6 6 -19 6q-5 0 -10 -2q-4 -1 -8 -4q-3 -3 -5 -8q-2 -4 -2 -9 h-28q0 10 4 18t11 14t17 10q9 3 21 3q11 0 22 -3q10 -3 16 -9q7 -6 11 -15t4 -20q0 -5 -2 -10q-1 -5 -4 -10q-4 -5 -8 -9q-5 -4 -11 -7q7 -3 13 -7q5 -4 8 -9q3 -4 5 -11q2 -5 2 -12q0 -11 -5 -20q-4 -9 -11.5 -15.5t-17.5 -9.5t-22 -3q-11 0 -21 3q-9 3 -17 9t-12 14.5 t-4 20.5h27q0 -6 2 -10.5t6 -7.5q3 -3 8 -5t11 -2zM360.5 255.5q10.5 -10.5 16.5 -25.5q5 -16 5 -34v-8q0 -19 -5 -34q-6 -15 -16 -25q-10 -11 -25 -17q-14 -5 -32 -5h-49v170h50q18 0 31.5 -5.5t24 -16zM352 188v8q0 28 -12 43q-12 14 -35 14h-20v-123h19q12 0 21 4t15 11 q6 8 9 19t3 24zM255 448q100 0 173.5 -68t81.5 -166h-32q-6 59 -40.5 107t-86.5 73l-29 -28l-81 81z" />
+<glyph unicode="&#xf102;" horiz-adv-x="405" d="M235 256l170 -107v-42l-67 21l-167 167v78q0 14 9 23t22.5 9t23 -9t9.5 -23v-117zM21 336l27 27l336 -336l-27 -27l-122 122v-79l42 -32v-32l-74 21l-75 -21v32l43 32v117l-171 -53v42l128 80z" />
+<glyph unicode="&#xf103;" horiz-adv-x="405" d="M175 256zM405 107l-170 53v-117l42 -32v-32l-74 21l-75 -21v32l43 32v117l-171 -53v42l171 107v117q0 14 9 23t22.5 9t23 -9t9.5 -23v-117l170 -107v-42z" />
+<glyph unicode="&#xf104;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213 96q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM213.5 213q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6z " />
+<glyph unicode="&#xf105;" horiz-adv-x="384" d="M374 336q10 -11 10 -27v-266q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v266q0 16 10 27l29 36q10 12 25 12h256q15 0 25 -12zM192 75l117 117h-74v43h-86v-43h-74zM45 341h294l-20 22h-256z" />
+<glyph unicode="&#xf106;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM192 384q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15t-15 6.5z M192 299q-27 0 -45.5 -19t-18.5 -45.5t18.5 -45t45.5 -18.5t45.5 18.5t18.5 45t-18.5 45.5t-45.5 19zM320 43v30q0 19 -23.5 35t-52.5 23.5t-52 7.5t-52 -7.5t-52.5 -23.5t-23.5 -35v-30h256z" />
+<glyph unicode="&#xf107;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM213 64v43h-42v-43h42zM213 149v128h-42v-128h42zM192 341q9 0 15 6.5t6 15 t-6 15t-15 6.5t-15 -6.5t-6 -15t6 -15t15 -6.5z" />
+<glyph unicode="&#xf108;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM192 384q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15t-15 6.5z M149 85l171 171l-30 30l-141 -140l-55 55l-30 -30z" />
+<glyph unicode="&#xf109;" horiz-adv-x="384" d="M341 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM192 405q-9 0 -15 -6t-6 -15t6 -15t15 -6t15 6t6 15t-6 15t-15 6zM341 21v342 h-42v-64h-214v64h-42v-342h298z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM192 384q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15t-15 6.5z M277 128v85h-85v64l-107 -106l107 -107v64h85z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 31t36.5 12t36.5 -12t23.5 -31h89zM192 384q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15t-15 6.5z M192 64l107 107h-64v85h-86v-85h-64z" />
+<glyph unicode="&#xf10c;" horiz-adv-x="384" d="M341 363q18 0 30.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h89q7 19 23.5 30.5t36.5 11.5t36.5 -11.5t23.5 -30.5h89zM192 363q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15 t-15 6.5zM235 64v43h-150v-43h150zM299 149v43h-214v-43h214zM299 235v42h-214v-42h214z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="235" d="M203 320h32v-245q0 -49 -34.5 -83.5t-83 -34.5t-83 34.5t-34.5 83.5v266q0 36 25 61t60.5 25t60.5 -25t25 -61v-224q0 -22 -16 -37.5t-38 -15.5t-37.5 15.5t-15.5 37.5v203h32v-203q0 -8 6.5 -14.5t15 -6.5t15 6.5t6.5 14.5v224q0 22 -16 38t-38 16t-37.5 -16t-15.5 -38 v-266q0 -36 25 -61t60.5 -25t60.5 25t25 61v245z" />
+<glyph unicode="&#xf10e;" d="M117 75q-48 0 -82.5 34t-34.5 83t34.5 83t82.5 34h224q36 0 61 -25t25 -60t-25 -60t-61 -25h-181q-22 0 -37.5 15.5t-15.5 37.5t15.5 37.5t37.5 15.5h160v-32h-160q-9 0 -15 -6t-6 -15t6 -15t15 -6h181q22 0 38 15.5t16 37.5t-16 37.5t-38 15.5h-224q-35 0 -60 -25 t-25 -60t25 -60t60 -25h203v-32h-203z" />
+<glyph unicode="&#xf10f;" horiz-adv-x="277" d="M128 384h149v-64h-85v-235h-1q-4 -36 -31 -60.5t-64 -24.5q-40 0 -68 28t-28 68t28 68t68 28q15 0 32 -6v198z" />
+<glyph unicode="&#xf110;" horiz-adv-x="384" d="M341 427q18 0 30.5 -12.5t12.5 -30.5v-276q0 -23 -19 -35l-173 -116l-173 116q-19 12 -19 35v276q0 18 12.5 30.5t30.5 12.5h298zM149 107l192 192l-30 30l-162 -162l-76 76l-30 -30z" />
+<glyph unicode="&#xf111;" horiz-adv-x="405" d="M384 64v-21q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298q18 0 30.5 -12.5t12.5 -30.5v-21h-192q-18 0 -30.5 -12.5t-12.5 -30.5v-170q0 -18 12.5 -30.5t30.5 -12.5h192zM192 107v170h213v-170h-213zM277.5 160 q13.5 0 22.5 9.5t9 22.5t-9 22.5t-22.5 9.5t-23 -9.5t-9.5 -22.5t9.5 -22.5t23 -9.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="405" d="M43 235h64v-150h-64v150zM171 235h64v-150h-64v150zM0 -21v64h405v-64h-405zM299 235h64v-150h-64v150zM203 427l202 -107v-43h-405v43z" />
+<glyph unicode="&#xf113;" horiz-adv-x="213" d="M185 363q12 0 20 -8.5t8 -20.5v-327q0 -12 -8 -20t-20 -8h-157q-11 0 -19.5 8t-8.5 20v327q0 12 8.5 20.5t19.5 8.5h36v42h85v-42h36zM128 64v43h-43v-43h43zM128 149v107h-43v-107h43z" />
+<glyph unicode="&#xf114;" horiz-adv-x="213" d="M185 363q12 0 20 -8.5t8 -20.5v-327q0 -12 -8 -20t-20 -8h-157q-11 0 -19.5 8t-8.5 20v327q0 12 8.5 20.5t19.5 8.5h36v42h85v-42h36zM85 21l86 160h-43v118l-85 -160h42v-118z" />
+<glyph unicode="&#xf115;" horiz-adv-x="213" d="M185 363q12 0 20 -8.5t8 -20.5v-327q0 -12 -8 -20t-20 -8h-157q-11 0 -19.5 8t-8.5 20v327q0 12 8.5 20.5t19.5 8.5h36v42h85v-42h36zM127 65v41h-41v-41h41zM156 177q15 15 15 36q0 27 -19 45.5t-45.5 18.5t-45 -18.5t-18.5 -45.5h32q0 14 9 23t22.5 9t23 -9t9.5 -22.5 t-10 -22.5l-20 -20q-19 -21 -19 -43h34q0 16 17 34z" />
+<glyph unicode="&#xf116;" horiz-adv-x="213" d="M185 363q12 0 20 -8.5t8 -20.5v-327q0 -12 -8 -20t-20 -8h-157q-11 0 -19.5 8t-8.5 20v327q0 12 8.5 20.5t19.5 8.5h36v42h85v-42h36z" />
+<glyph unicode="&#xf117;" horiz-adv-x="512" d="M330.5 331q-17.5 0 -30 12.5t-12.5 30t12.5 30t30 12.5t30 -12.5t12.5 -30t-12.5 -30t-30 -12.5zM106.5 192q44.5 0 75.5 -31t31 -75.5t-31 -75.5t-75.5 -31t-75.5 31t-31 75.5t31 75.5t75.5 31zM106.5 11q30.5 0 52.5 22t22 52.5t-22 52.5t-52.5 22t-52.5 -22t-22 -52.5 t22 -52.5t52.5 -22zM230 224l47 -49v-132h-42v106l-69 60q-12 10 -12 30q0 17 12 30l60 60q10 12 30 12q18 0 34 -12l41 -41q32 -32 76 -32v-43q-64 0 -108 45l-17 17zM405.5 192q44.5 0 75.5 -31t31 -75.5t-31 -75.5t-75.5 -31t-75.5 31t-31 75.5t31 75.5t75.5 31z M405.5 11q30.5 0 52.5 22t22 52.5t-22 52.5t-52.5 22t-52.5 -22t-22 -52.5t22 -52.5t52.5 -22z" />
+<glyph unicode="&#xf118;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213 21q58 0 105 36l-239 240q-36 -47 -36 -105q0 -71 50 -121t120 -50zM348 87q36 47 36 105q0 71 -50 121t-121 50q-58 0 -104 -36z" />
+<glyph unicode="&#xf119;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM43 192q0 -59 36 -105l239 240q-46 36 -105 36q-70 0 -120 -50t-50 -121zM213 21q71 0 121 50t50 121q0 59 -36 105l-239 -240q46 -36 104 -36z" />
+<glyph unicode="&#xf11a;" d="M384 0h43v-43h-43q-44 0 -85 21q-41 -20 -86 -20t-85 20q-42 -21 -85 -21h-43v43h43q45 0 85 28q39 -27 85.5 -27t85.5 27q40 -28 85 -28zM42 43l-41 142q-3 8 1 17q4 8 13 10l28 9v99q0 18 12.5 30.5t29.5 12.5h64v64h128v-64h64q18 0 30.5 -12.5t12.5 -30.5v-99l27 -9 q9 -2 13 -10t1 -17l-40 -142h-1q-48 0 -85 42q-38 -42 -86 -42t-85 42q-37 -42 -85 -42h-1zM85 320v-85l128 42l128 -42v85h-256z" />
+<glyph unicode="&#xf11b;" horiz-adv-x="341" d="M299 405q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h256zM43 363v-171l53 32l53 -32v171h-106zM43 43h256l-83 109l-64 -82l-45 55z" />
+<glyph unicode="&#xf11c;" horiz-adv-x="341" d="M299 405q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h256zM43 363v-171l53 32l53 -32v171h-106z" />
+<glyph unicode="&#xf11d;" horiz-adv-x="299" d="M256 384q18 0 30.5 -12.5t12.5 -30.5v-341l-150 64l-149 -64v341q0 18 12.5 30.5t30.5 12.5h213zM256 64v277h-213v-277l106 47z" />
+<glyph unicode="&#xf11e;" horiz-adv-x="299" d="M256 384q18 0 30.5 -12.5t12.5 -30.5v-341l-150 64l-149 -64v341q0 18 12.5 30.5t30.5 12.5h213z" />
+<glyph unicode="&#xf11f;" horiz-adv-x="405" d="M106.5 149q26.5 0 45.5 -18.5t19 -45.5q0 -35 -25 -60t-61 -25q-24 0 -47 11.5t-38 31.5q15 0 29 11.5t14 30.5q0 27 18.5 45.5t45 18.5zM399 349q6 -6 6 -15t-6 -15l-191 -191l-59 59l191 191q7 6 15.5 6t15.5 -6z" />
+<glyph unicode="&#xf120;" horiz-adv-x="341" d="M341 277v-42h-44q2 -13 2 -22v-21h42v-43h-42v-21q0 -9 -2 -21h44v-43h-60q-17 -29 -46 -46.5t-64 -17.5t-64.5 17.5t-46.5 46.5h-60v43h45q-2 12 -2 21v21h-43v43h43v21q0 9 2 22h-45v42h60q15 26 39 42l-35 35l30 30l47 -46q14 3 29.5 3t30.5 -3l46 46l30 -30l-34 -35 q24 -16 38 -42h60zM213 107v42h-85v-42h85zM213 192v43h-85v-43h85z" />
+<glyph unicode="&#xf121;" horiz-adv-x="341" d="M0 107v213q0 27 12.5 44.5t38 26t53 11.5t67 3t67 -3t53 -11.5t38 -26t12.5 -44.5v-213q0 -28 -21 -48v-38q0 -8 -6.5 -14.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 14.5v22h-171v-22q0 -8 -6 -14.5t-15 -6.5h-21q-9 0 -15.5 6.5t-6.5 14.5v38q-21 20 -21 48zM74.5 85 q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM266.5 85q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM299 213v107h-256v-107h256z" />
+<glyph unicode="&#xf122;" horiz-adv-x="384" d="M192 320q-18 0 -30.5 12.5t-12.5 30.5q0 12 7 22l36 63l36 -63q7 -10 7 -22q0 -18 -12.5 -30.5t-30.5 -12.5zM290 107q22 -22 52 -22q23 0 42 13v-98q0 -9 -6.5 -15t-14.5 -6h-342q-8 0 -14.5 6t-6.5 15v98q19 -13 42 -13q30 0 52 22l23 23l23 -23q21 -21 52 -21t52 21 l23 23zM320 256q27 0 45.5 -18.5t18.5 -45.5v-33q0 -17 -12.5 -29.5t-29.5 -12.5t-29 12l-46 46l-46 -46q-11 -11 -29 -11t-30 11l-45 46l-46 -46q-12 -12 -29 -12t-29.5 12.5t-12.5 29.5v33q0 27 18.5 45.5t45.5 18.5h107v43h42v-43h107z" />
+<glyph unicode="&#xf123;" horiz-adv-x="384" d="M340 320l44 -128v-171q0 -8 -6.5 -14.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 14.5v22h-256v-22q0 -8 -6.5 -14.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 14.5v171l44 128q8 21 31 21h53v43h128v-43h53q23 0 31 -21zM74.5 107q13.5 0 23 9t9.5 22.5t-9.5 23t-23 9.5 t-22.5 -9.5t-9 -23t9 -22.5t22.5 -9zM309.5 107q13.5 0 22.5 9t9 22.5t-9 23t-22.5 9.5t-23 -9.5t-9.5 -23t9.5 -22.5t23 -9zM43 213h298l-32 96h-234z" />
+<glyph unicode="&#xf124;" horiz-adv-x="384" d="M298.5 341q-13.5 0 -22.5 9.5t-9 22.5q0 10 8 24.5t16 23.5l8 10q32 -36 32 -58q0 -13 -9.5 -22.5t-23 -9.5zM192 341q-13 0 -22.5 9.5t-9.5 22.5q0 10 8 24.5t16 23.5l8 10q32 -36 32 -58q0 -13 -9.5 -22.5t-22.5 -9.5zM85.5 341q-13.5 0 -23 9.5t-9.5 22.5q0 10 8 24.5 t16 23.5l8 10q32 -36 32 -58q0 -13 -9 -22.5t-22.5 -9.5zM340 277l44 -128v-170q0 -9 -6.5 -15.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 15.5v21h-256v-21q0 -9 -6.5 -15.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 15.5v170l44 128q8 22 31 22h234q23 0 31 -22zM74.5 64 q13.5 0 23 9.5t9.5 22.5t-9.5 22.5t-23 9.5t-22.5 -9.5t-9 -22.5t9 -22.5t22.5 -9.5zM309.5 64q13.5 0 22.5 9.5t9 22.5t-9 22.5t-22.5 9.5t-23 -9.5t-9.5 -22.5t9.5 -22.5t23 -9.5zM43 171h298l-32 96h-234z" />
+<glyph unicode="&#xf125;" horiz-adv-x="384" d="M340 320l44 -128v-171q0 -8 -6.5 -14.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 14.5v22h-256v-22q0 -8 -6.5 -14.5t-14.5 -6.5h-22q-8 0 -14.5 6.5t-6.5 14.5v171l44 128q8 21 31 21h234q23 0 31 -21zM74.5 107q13.5 0 23 9t9.5 22.5t-9.5 23t-23 9.5t-22.5 -9.5t-9 -23 t9 -22.5t22.5 -9zM309.5 107q13.5 0 22.5 9t9 22.5t-9 23t-22.5 9.5t-23 -9.5t-9.5 -23t9.5 -22.5t23 -9zM43 213h298l-32 96h-234z" />
+<glyph unicode="&#xf126;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-234q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v234q0 18 12.5 30.5t30.5 12.5h46q-4 11 -4 21q0 27 19 45.5t45 18.5q34 0 54 -28l10 -15l11 15q19 28 53 28q27 0 45.5 -18.5t18.5 -45.5q0 -10 -4 -21h47z M277.5 363q-8.5 0 -15 -6.5t-6.5 -15t6.5 -15t15 -6.5t15 6.5t6.5 15t-6.5 15t-15 6.5zM149.5 363q-8.5 0 -15 -6.5t-6.5 -15t6.5 -15t15 -6.5t15 6.5t6.5 15t-6.5 15t-15 6.5zM384 43v42h-341v-42h341zM384 149v128h-108l44 -60l-35 -25l-50 69l-22 29l-21 -29l-51 -69 l-34 25l44 60h-108v-128h341z" />
+<glyph unicode="&#xf127;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-235q0 -18 -12.5 -30.5t-30.5 -12.5h-85v-106l-86 42l-85 -42v106h-85q-18 0 -30.5 12.5t-12.5 30.5v235q0 17 12.5 29.5t30.5 12.5h341zM384 128v43h-341v-43h341zM384 235v128h-341v-128h341z" />
+<glyph unicode="&#xf128;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-234q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v234q0 18 12.5 30.5t30.5 12.5h64v43q0 17 12.5 29.5t29.5 12.5h128q18 0 30.5 -12.5t12.5 -29.5v-43h64zM149 363v-43h128v43h-128zM384 43v42h-341v-42h341z M384 149v128h-64v-42h-43v42h-128v-42h-42v42h-64v-128h341z" />
+<glyph unicode="&#xf129;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM384 64v128h-341v-128h341zM384 277v43h-341v-43h341z" />
+<glyph unicode="&#xf12a;" d="M384 309q18 0 30.5 -12.5t12.5 -29.5v-235q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v235q0 17 12.5 29.5t30.5 12.5h85v43l43 43h85l43 -43v-43h85zM171 352v-43h85v43h-85zM181 64l141 141l-30 30l-111 -111l-44 45l-30 -30z" />
+<glyph unicode="&#xf12b;" d="M384 309q18 0 30.5 -12.5t12.5 -29.5v-235q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v235q0 17 12.5 29.5t30.5 12.5h85v43l43 43h85l43 -43v-43h85zM171 352v-43h85v43h-85zM213 32l107 107h-64v85h-85v-85h-64z" />
+<glyph unicode="&#xf12c;" d="M299 320h128v-277q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v277h128v43q0 17 12.5 29.5t30.5 12.5h85q18 0 30.5 -12.5t12.5 -29.5v-43zM171 363v-43h85v43h-85zM149 64l160 107l-160 85v-192z" />
+<glyph unicode="&#xf12d;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-234q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v234q0 18 12.5 30.5t30.5 12.5h85v43q0 17 12.5 29.5t30.5 12.5h85q18 0 30.5 -12.5t12.5 -29.5v-43h85zM256 320v43h-85v-43h85z" />
+<glyph unicode="&#xf12e;" horiz-adv-x="469" d="M0 64q27 0 45.5 -18.5t18.5 -45.5h-64v64zM0 149q62 0 105.5 -43.5t43.5 -105.5h-42q0 44 -31.5 75.5t-75.5 31.5v42zM384 299v-214h-120q-21 64 -68 111t-111 68v35h299zM0 235q97 0 166 -69t69 -166h-43q0 80 -56 136t-136 56v43zM427 384q17 0 29.5 -12.5t12.5 -30.5 v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-150v43h150v298h-384v-64h-43v64q0 18 12.5 30.5t30.5 12.5h384z" />
+<glyph unicode="&#xf12f;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-150v43h150v298h-384v-64h-43v64q0 18 12.5 30.5t30.5 12.5h384zM0 64q27 0 45.5 -18.5t18.5 -45.5h-64v64zM0 149q62 0 105.5 -43.5t43.5 -105.5h-42q0 44 -31.5 75.5t-75.5 31.5v42zM0 235 q97 0 166 -69t69 -166h-43q0 80 -56 136t-136 56v43z" />
+<glyph unicode="&#xf130;" d="M235 404q81 -8 136.5 -68.5t55.5 -143.5q0 -45 -19 -87l-56 33q11 27 11 54q0 56 -37 98t-91 50v64zM213 43q72 0 117 56l55 -33q-30 -41 -75 -64t-97 -23q-88 0 -150.5 62.5t-62.5 150.5q0 83 55.5 143.5t136.5 68.5v-64q-55 -8 -91.5 -50t-36.5 -98q0 -62 43.5 -105.5 t105.5 -43.5z" />
+<glyph unicode="&#xf131;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM128 85v150h-43v-150h43zM213 85v214h-42v-214h42zM299 85v86h-43v-86h43z" />
+<glyph unicode="&#xf132;" d="M213 299h214v-299h-427v384h213v-85zM85 43v42h-42v-42h42zM85 128v43h-42v-43h42zM85 213v43h-42v-43h42zM85 299v42h-42v-42h42zM171 43v42h-43v-42h43zM171 128v43h-43v-43h43zM171 213v43h-43v-43h43zM171 299v42h-43v-42h43zM384 43v213h-171v-43h43v-42h-43v-43h43 v-43h-43v-42h171zM341 213v-42h-42v42h42zM341 128v-43h-42v43h42z" />
+<glyph unicode="&#xf133;" horiz-adv-x="384" d="M256 213h128v-213h-384v299h128v42l64 64l64 -64v-128zM85 43v42h-42v-42h42zM85 128v43h-42v-43h42zM85 213v43h-42v-43h42zM213 43v42h-42v-42h42zM213 128v43h-42v-43h42zM213 213v43h-42v-43h42zM213 299v42h-42v-42h42zM341 43v42h-42v-42h42zM341 128v43h-42v-43 h42z" />
+<glyph unicode="&#xf134;" d="M269 277l30 -30l-56 -55l56 -55l-30 -30l-56 55l-55 -55l-30 30l55 55l-55 55l30 30l55 -55zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50 t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf135;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM320 115l-77 77l77 77l-30 30l-77 -77l-76 77l-30 -30l76 -77l-76 -77l30 -30l76 77l77 -77z" />
+<glyph unicode="&#xf136;" horiz-adv-x="299" d="M299 311l-120 -119l120 -119l-30 -30l-120 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l120 119z" />
+<glyph unicode="&#xf137;" horiz-adv-x="384" d="M171 171l-171 170v43h384v-43l-171 -170v-128h107v-43h-256v43h107v128zM96 299h192l43 42h-278z" />
+<glyph unicode="&#xf138;" horiz-adv-x="477" d="M148 304l-93 -112l93 -112l-33 -27l-115 139l115 139zM132 171v42h43v-42h-43zM345 213v-42h-42v42h42zM217 171v42h43v-42h-43zM362 331l115 -139l-115 -139l-33 27l93 112l-93 112z" />
+<glyph unicode="&#xf139;" horiz-adv-x="341" d="M64 341v-42h-43v85q0 18 12.5 30.5t30.5 12.5l213 -1q18 0 30.5 -12t12.5 -30v-85h-43v42h-213zM243 94l-30 30l68 68l-68 68l30 30l98 -98zM128 124l-30 -30l-98 98l98 98l30 -30l-68 -68zM277 43v42h43v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5 t-12.5 30.5v85h43v-42h213z" />
+<glyph unicode="&#xf13a;" d="M158 94l-30 -30l-128 128l128 128l30 -30l-98 -98zM269 94l98 98l-98 98l30 30l128 -128l-128 -128z" />
+<glyph unicode="&#xf13b;" d="M384 384q18 0 30.5 -12.5t12.5 -30.5v-64q0 -17 -12.5 -29.5t-30.5 -12.5h-43v-64q0 -36 -25 -61t-60 -25h-128q-35 0 -60 25t-25 61v213h341zM384 277v64h-43v-64h43zM0 0v43h384v-43h-384z" />
+<glyph unicode="&#xf13c;" d="M43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43zM384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM384 192v171h-107v-171l54 32z" />
+<glyph unicode="&#xf13d;" horiz-adv-x="469" d="M43 256v-235h341q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v235h43zM363 341h106v-234q0 -18 -12.5 -30.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 30.5v234h107v43q0 18 12.5 30.5t30.5 12.5h85q18 0 30.5 -12.5t12.5 -30.5v-43zM235 384v-43 h85v43h-85zM235 128l117 85l-117 64v-149z" />
+<glyph unicode="&#xf13e;" horiz-adv-x="512" d="M43 320v-299h384v-42h-384q-18 0 -30.5 12.5t-12.5 29.5v192v107h43zM469 363q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5l1 256q0 17 12 29.5t30 12.5h128l43 -42h170zM149 128h299l-75 96l-53 -64l-75 96z" />
+<glyph unicode="&#xf13f;" horiz-adv-x="469" d="M319 228l76 -100h-235l59 75l41 -50zM43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299 v-299h299z" />
+<glyph unicode="&#xf140;" d="M427 107q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256q18 0 30.5 -12.5t12.5 -29.5v-256zM192 192l-64 -85h256l-85 106l-64 -79zM0 320h43v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299z" />
+<glyph unicode="&#xf141;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM277 128v171h-42v42h85v-213h-43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299 h299z" />
+<glyph unicode="&#xf142;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM341 171v-43h-128v85 q0 18 12.5 30.5t30.5 12.5h43v43h-86v42h86q17 0 29.5 -12.5t12.5 -29.5v-43q0 -18 -12.5 -30.5t-29.5 -12.5h-43v-42h85z" />
+<glyph unicode="&#xf143;" horiz-adv-x="469" d="M427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM341 171q0 -18 -12.5 -30.5 t-29.5 -12.5h-86v43h86v42h-43v43h43v43h-86v42h86q17 0 29.5 -12.5t12.5 -29.5v-32q0 -14 -9 -23t-23 -9q14 0 23 -9.5t9 -22.5v-32z" />
+<glyph unicode="&#xf144;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM299 128v85h-86v128h43v-85h43v85h42v-213h-42zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85 v299h-299v-299h299z" />
+<glyph unicode="&#xf145;" horiz-adv-x="469" d="M427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM341 171q0 -18 -12.5 -30.5 t-29.5 -12.5h-86v43h86v42h-86v128h128v-42h-85v-43h43q17 0 29.5 -12.5t12.5 -30.5v-42z" />
+<glyph unicode="&#xf146;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM256 128q-18 0 -30.5 12.5 t-12.5 30.5v128q0 17 12.5 29.5t30.5 12.5h85v-42h-85v-43h43q17 0 29.5 -12.5t12.5 -30.5v-42q0 -18 -12.5 -30.5t-29.5 -12.5h-43zM256 213v-42h43v42h-43z" />
+<glyph unicode="&#xf147;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM256 128h-43l86 171h-86v42 h128v-42z" />
+<glyph unicode="&#xf148;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM256 128q-18 0 -30.5 12.5 t-12.5 30.5v32q0 13 9.5 22.5t22.5 9.5q-13 0 -22.5 9t-9.5 23v32q0 17 12.5 29.5t30.5 12.5h43q17 0 29.5 -12.5t12.5 -29.5v-32q0 -14 -9 -23t-23 -9q14 0 23 -9.5t9 -22.5v-32q0 -18 -12.5 -30.5t-29.5 -12.5h-43zM256 299v-43h43v43h-43zM256 213v-42h43v42h-43z" />
+<glyph unicode="&#xf149;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM277 192q0 -18 -12.5 -30.5t-29.5 -12.5h-64v43h64v21h-22q-17 0 -29.5 12.5t-12.5 30.5v21q0 18 12.5 30.5t29.5 12.5h22q17 0 29.5 -12.5t12.5 -30.5v-85zM213 256h22v21h-22v-21zM427 427 q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 256v128h-299v-299h299v128h-43v-42h-43v42h-42v43h42v43h43v-43h43z" />
+<glyph unicode="&#xf14a;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299zM299 341q17 0 29.5 -12.5 t12.5 -29.5v-128q0 -18 -12.5 -30.5t-29.5 -12.5h-86v43h86v42h-43q-18 0 -30.5 12.5t-12.5 30.5v43q0 17 12.5 29.5t30.5 12.5h43zM299 256v43h-43v-43h43z" />
+<glyph unicode="&#xf14b;" horiz-adv-x="469" d="M43 341v-341h341v-43h-341q-18 0 -30.5 12.5t-12.5 30.5v341h43zM427 427q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-299q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h299zM427 85v299h-299v-299h299z" />
+<glyph unicode="&#xf14c;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM341 299v42h-85v-117q-14 11 -32 11q-22 0 -37.5 -16t-15.5 -38t15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5v118h64z M43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43z" />
+<glyph unicode="&#xf14d;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM203 245v22q0 13 -9.5 22.5t-22.5 9.5h-54v-128h32v42h22q13 0 22.5 9.5t9.5 22.5zM309 203v64q0 13 -9 22.5t-23 9.5h-53 v-128h53q14 0 23 9t9 23zM395 267v32h-64v-128h32v42h32v32h-32v22h32zM149 245v22h22v-22h-22zM43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43zM256 203v64h21v-64h-21z" />
+<glyph unicode="&#xf14e;" d="M43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43zM384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM363 213v43h-86v85h-42v-85h-86v-43h86v-85h42v85h86z " />
+<glyph unicode="&#xf14f;" horiz-adv-x="341" d="M303 427q16 0 27 -11.5t11 -27.5v-307q0 -16 -11 -27t-27 -11h-179q-16 0 -27.5 11t-11.5 27v307q0 16 11.5 27.5t27.5 11.5h179zM213.5 384q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5zM213.5 96q35.5 0 60.5 25t25 60.5 t-25 60.5t-60.5 25t-60.5 -25t-25 -60.5t25 -60.5t60.5 -25zM160 181.5q0 53.5 53.5 53.5t53.5 -53.5t-53.5 -53.5t-53.5 53.5zM43 341v-341h213v-43h-213q-18 0 -30.5 12.5t-12.5 30.5v341h43z" />
+<glyph unicode="&#xf150;" d="M43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43zM384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM363 213v43h-214v-43h214zM277 128v43h-128v-43h128z M363 299v42h-214v-42h214z" />
+<glyph unicode="&#xf151;" d="M43 320v-299h298v-42h-298q-18 0 -30.5 12.5t-12.5 29.5v299h43zM384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h256zM213 139l128 96l-128 96v-192z" />
+<glyph unicode="&#xf152;" d="M213.5 215q9.5 0 16.5 -6.5t7 -16.5t-7 -16.5t-16.5 -6.5t-16.5 6.5t-7 16.5t7 16.5t16.5 6.5zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM260 145l81 175l-174 -81l-82 -175z" />
+<glyph unicode="&#xf153;" horiz-adv-x="408" d="M114 163l-89 90q-25 25 -25 60t25 60l150 -149zM259 202l-31 -31l146 -147l-30 -30l-146 147l-147 -147l-31 30l209 208q-12 24 -4 56t33 57q31 30 69 35t61.5 -18.5t18.5 -61.5t-36 -69q-25 -25 -56.5 -33t-55.5 4z" />
+<glyph unicode="&#xf154;" horiz-adv-x="299" d="M21 43v256h256v-256q0 -18 -12.5 -30.5t-29.5 -12.5h-171q-18 0 -30.5 12.5t-12.5 30.5zM299 363v-43h-299v43h75l21 21h107l21 -21h75z" />
+<glyph unicode="&#xf155;" horiz-adv-x="341" d="M170.5 43q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM42.5 427q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM42.5 299q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5 t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM42.5 171q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM298.5 341q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5z M170.5 171q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM298.5 171q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM298.5 299q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5 t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM170.5 299q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM170.5 427q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5z" />
+<glyph unicode="&#xf156;" horiz-adv-x="384" d="M363 171q8 0 14.5 -6.5t6.5 -15.5v-128q0 -8 -6.5 -14.5t-14.5 -6.5h-342q-8 0 -14.5 6.5t-6.5 14.5v128q0 9 6.5 15.5t14.5 6.5h342zM85.5 43q17.5 0 30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30t30 -12.5zM363 384q8 0 14.5 -6.5t6.5 -14.5v-128 q0 -9 -6.5 -15.5t-14.5 -6.5h-342q-8 0 -14.5 6.5t-6.5 15.5v128q0 8 6.5 14.5t14.5 6.5h342zM85.5 256q17.5 0 30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30t30 -12.5z" />
+<glyph unicode="&#xf157;" horiz-adv-x="384" d="M0 405h384l-43 -389q-2 -16 -14 -26.5t-28 -10.5h-214q-16 0 -28 10.5t-14 26.5zM192 43q27 0 45.5 18.5t18.5 45.5q0 19 -16 47.5t-32 48.5l-16 19q-7 -8 -17.5 -21.5t-28.5 -44t-18 -49.5q0 -27 18.5 -45.5t45.5 -18.5zM327 277l9 86h-288l9 -86h270z" />
+<glyph unicode="&#xf158;" horiz-adv-x="384" d="M0 80l236 236l80 -80l-236 -236h-80v80zM378 298l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15z" />
+<glyph unicode="&#xf159;" d="M426 277l1 -213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v213q0 24 20 37l193 113l193 -113q20 -13 20 -37zM213 171l177 110l-177 103l-176 -103z" />
+<glyph unicode="&#xf15a;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM384 277v43l-171 -107l-170 107v-43l170 -106z" />
+<glyph unicode="&#xf15b;" horiz-adv-x="469" d="M235 299q-20 0 -39 -8l-46 46q41 15 84 15q79 0 143 -44.5t92 -115.5q-23 -60 -73 -101l-62 62q7 19 7 39q0 44 -31 75.5t-75 31.5zM21 357l27 27l379 -378l-27 -27l-63 62l-9 9q-45 -18 -93 -18q-79 0 -143 44.5t-92 115.5q25 64 80 106l-10 10zM139 239 q-11 -23 -11 -47q0 -44 31.5 -75.5t75.5 -31.5q24 0 47 12l-33 33q-8 -2 -14 -2q-27 0 -45.5 18.5t-18.5 45.5q0 7 1 14zM231 256h4q26 0 45 -19t19 -45l-1 -4z" />
+<glyph unicode="&#xf15c;" horiz-adv-x="469" d="M235 352q79 0 142.5 -44.5t91.5 -115.5q-28 -71 -91.5 -115.5t-142.5 -44.5t-143 44.5t-92 115.5q28 71 92 115.5t143 44.5zM235 85q44 0 75 31.5t31 75.5t-31 75.5t-75 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM234.5 256q26.5 0 45.5 -18.5t19 -45.5 t-19 -45.5t-45.5 -18.5t-45 18.5t-18.5 45.5t18.5 45.5t45 18.5z" />
+<glyph unicode="&#xf15d;" horiz-adv-x="384" d="M378 328q6 -6 6 -15t-6 -15l-67 -67l41 -41l-30 -30l-30 30l-191 -190h-101v101l190 191l-30 30l30 30l41 -41l67 67q6 6 15 6t15 -6zM84 43l172 172l-41 41l-172 -172z" />
+<glyph unicode="&#xf15e;" d="M309 384q50 0 84 -34t34 -83q0 -24 -10 -48.5t-22 -43.5t-40.5 -49t-48 -48.5t-62.5 -56.5l-31 -28l-31 27q-42 39 -62 57.5t-48.5 48.5t-40.5 49t-21.5 43.5t-9.5 48.5q0 49 34 83t83 34q58 0 96 -45q38 45 96 45zM215 52q49 44 71.5 65.5t49.5 51.5t37.5 52.5 t10.5 45.5q0 32 -21.5 53t-53.5 21q-24 0 -45.5 -14t-30.5 -36h-40q-8 22 -29.5 36t-46.5 14q-32 0 -53 -21t-21 -53q0 -23 10 -45.5t37.5 -52.5t50 -51.5t70.5 -65.5l2 -2z" />
+<glyph unicode="&#xf15f;" d="M213 -7l-31 28q-42 38 -62 56.5t-48 48.5t-40.5 49t-22 43.5t-9.5 48.5q0 49 34 83t83 34q58 0 96 -45q38 45 96 45q50 0 84 -34t34 -83q0 -24 -10 -48.5t-22 -43.5t-40.5 -49t-48 -48.5t-62.5 -57.5z" />
+<glyph unicode="&#xf160;" horiz-adv-x="384" d="M149 64v43h86v-43h-86zM0 320h384v-43h-384v43zM64 171v42h256v-42h-256z" />
+<glyph unicode="&#xf161;" horiz-adv-x="341" d="M203 434q64 -52 101 -126t37 -159q0 -70 -50 -120t-120.5 -50t-120.5 50t-50 120q0 108 69 190l-1 -8q0 -33 22.5 -56t55.5 -23q32 0 52 23t20 56q0 21 -3.5 46.5t-7.5 40.5zM164 43q43 0 73 30t30 72q0 45 -13 86q-30 -41 -98 -55q-29 -6 -44.5 -23.5t-15.5 -42.5 q0 -28 20 -47.5t48 -19.5z" />
+<glyph unicode="&#xf162;" horiz-adv-x="320" d="M201 320h119v-213h-149l-9 42h-119v-149h-43v363h192z" />
+<glyph unicode="&#xf163;" horiz-adv-x="469" d="M128 213v-42h-128v42h128zM174 282l-30 -30l-45 46l30 30zM256 427v-128h-43v128h43zM370 298l-45 -46l-30 30l45 46zM341 213h128v-42h-128v42zM234.5 256q26.5 0 45.5 -18.5t19 -45.5t-19 -45.5t-45.5 -18.5t-45 18.5t-18.5 45.5t18.5 45.5t45 18.5zM295 102l30 30 l45 -46l-30 -30zM99 86l45 46l30 -30l-45 -46zM213 -43v128h43v-128h-43z" />
+<glyph unicode="&#xf164;" horiz-adv-x="410" d="M0 405h213l-85 -192h85l-149 -256v192h-64v256zM341 405l69 -192h-41l-15 43h-68l-15 -43h-41l69 192h42zM295 285h50l-25 78z" />
+<glyph unicode="&#xf165;" horiz-adv-x="363" d="M27 384l336 -336l-27 -27l-89 89l-76 -131v192h-64v79l-107 107zM320 235l-33 -57l-180 181v46h213l-85 -170h85z" />
+<glyph unicode="&#xf166;" horiz-adv-x="213" d="M0 405h213l-85 -170h85l-149 -256v192h-64v234z" />
+<glyph unicode="&#xf167;" horiz-adv-x="384" d="M256 0v43h43v-43h-43zM341 256v43h43v-43h-43zM0 341q0 18 12.5 30.5t30.5 12.5h85v-43h-85v-298h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v298zM341 384q18 0 30.5 -12.5t12.5 -30.5h-43v43zM171 -43v470h42v-470h-42zM341 85v43h43v-43h-43zM256 341v43h43v-43h-43z M341 171v42h43v-42h-43zM341 0v43h43q0 -18 -12.5 -30.5t-30.5 -12.5z" />
+<glyph unicode="&#xf168;" horiz-adv-x="384" d="M192 -21q0 79 56 135.5t136 56.5q0 -80 -56 -136t-136 -56zM55 229q0 34 31 48q-31 15 -31 48q0 22 16 38t38 16q17 0 30 -10v4q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-4q14 10 30 10q22 0 38 -16t16 -38q0 -33 -31 -48q31 -14 31 -48q0 -22 -16 -37.5t-38 -15.5 q-17 0 -30 9v-4q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v4q-14 -9 -30 -9q-22 0 -38 15.5t-16 37.5zM192 331q-22 0 -37.5 -16t-15.5 -38t15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 38t-37.5 16zM0 171q80 0 136 -56.5t56 -135.5q-80 0 -136 56 t-56 136z" />
+<glyph unicode="&#xf169;" horiz-adv-x="414" d="M350 183q30 -17 47 -47t17 -63q-29 -17 -63 -17.5t-65 17.5q-9 5 -17 11q2 -10 2 -20q0 -35 -17.5 -64.5t-46.5 -46.5q-29 17 -46.5 46.5t-17.5 64.5q0 10 2 20q-9 -7 -17 -11q-31 -17 -65 -17t-63 17q0 34 17 63.5t47 47.5q8 4 18 8q-10 4 -18 9q-30 17 -47 47t-17 63 q29 17 63 17.5t65 -17.5q8 -4 17 -11q-2 10 -2 20q0 35 17.5 64.5t46.5 46.5q29 -17 46.5 -46.5t17.5 -64.5q0 -10 -2 -20q9 7 17 11q31 18 65 17.5t63 -17.5q0 -33 -17 -63t-47 -47q-8 -5 -18 -9q10 -4 18 -9zM207 107q35 0 60 25t25 60t-25 60t-60 25t-60 -25t-25 -60 t25 -60t60 -25z" />
+<glyph unicode="&#xf16a;" d="M169 160l44 118l44 -118h-88zM384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341zM298 53h44l-109 278h-40l-109 -278h45l24 64h120z" />
+<glyph unicode="&#xf16b;" horiz-adv-x="469" d="M235 331l42 -54h-85zM363 235l53 -43l-53 -43v86zM107 235v-86l-54 43zM277 107l-42 -54l-43 54h85zM427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384 v-300h384z" />
+<glyph unicode="&#xf16c;" horiz-adv-x="299" d="M0 107v42h107v-106h-43v64h-64zM64 277v64h43v-106h-107v42h64zM192 43v106h107v-42h-64v-64h-43zM235 277h64v-42h-107v106h43v-64z" />
+<glyph unicode="&#xf16d;" horiz-adv-x="299" d="M43 149v-64h64v-42h-107v106h43zM0 235v106h107v-42h-64v-64h-43zM256 85v64h43v-106h-107v42h64zM192 341h107v-106h-43v64h-64v42z" />
+<glyph unicode="&#xf16e;" horiz-adv-x="256" d="M256 363v-64h-149l106 -107l-106 -107h149v-64h-256v43l139 128l-139 128v43h256z" />
+<glyph unicode="&#xf16f;" horiz-adv-x="352" d="M336 294q16 -16 16 -38v-203q0 -22 -15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5v107h-32v-160h-213v341q0 18 12.5 30.5t30.5 12.5h128q17 0 29.5 -12.5t12.5 -30.5v-149h22q17 0 29.5 -12.5t12.5 -30.5v-96q0 -8 6.5 -14.5t15 -6.5t15 6.5t6.5 14.5v154q-11 -4 -21 -4 q-22 0 -38 15.5t-16 37.5q0 17 9.5 30.5t25.5 19.5l-45 45l22 22l80 -79h-1zM171 235v106h-128v-106h128zM298.5 235q8.5 0 15 6t6.5 15t-6.5 15t-15 6t-15 -6t-6.5 -15t6.5 -15t15 -6z" />
+<glyph unicode="&#xf170;" horiz-adv-x="387" d="M37 301l-37 36q5 6 19 20q26 27 58 27q18 0 35.5 -15t17.5 -46q0 -20 -6 -34t-21 -36q-29 -43 -40 -75q-5 -18 -2.5 -29.5t10.5 -11.5q9 0 24 18q16 17 48 58q18 22 46 41t60 19q42 0 62.5 -27.5t23.5 -61.5h52v-53h-52q-6 -69 -36.5 -100t-63.5 -31q-28 0 -48.5 19.5 t-20.5 46.5q0 33 30 69.5t85 45.5v3q-1 8 -2.5 12.5t-5 10.5t-11 9t-18.5 3q-18 0 -39 -20t-48 -53q-16 -19 -23.5 -28t-19.5 -18.5t-23 -12.5q-30 -10 -56 9q-29 22 -29 64q0 14 6 32.5t15 35.5t16.5 30t15.5 24.5t8 12.5q18 28 7 32q-8 3 -37 -26zM236 52q14 0 27.5 18 t17.5 57q-30 -8 -45.5 -27t-15.5 -32q0 -7 5 -11.5t11 -4.5z" />
+<glyph unicode="&#xf171;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM361 277q-32 56 -92 76q19 -35 29 -76h63zM213 362q-27 -39 -40 -85h81q-14 46 -41 85zM48 149h72q-3 25 -3 43t3 43h-72q-5 -23 -5 -43t5 -43zM66 107 q32 -56 92 -76q-19 35 -29 76h-63zM129 277q10 41 29 76q-60 -20 -92 -76h63zM213 22q27 39 41 85h-81q13 -46 40 -85zM263 149q4 25 4 43t-4 43h-100q-3 -25 -3 -43t3 -43h100zM269 31q60 20 92 76h-63q-10 -41 -29 -76zM306 149h72q6 23 6 43t-6 43h-72q3 -25 3 -43 t-3 -43z" />
+<glyph unicode="&#xf172;" horiz-adv-x="469" d="M448 363q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-8 0 -14.5 6t-6.5 15v85q0 9 6.5 15.5t14.5 6.5v10q0 22 16 38t38 16t37.5 -16t15.5 -38v-10zM431 363v10q0 15 -10.5 26t-25.5 11t-26 -11t-11 -26v-10h73zM382 192h44q1 -12 1 -21q0 -89 -62.5 -151.5 t-151 -62.5t-151 62.5t-62.5 151t62.5 151t150.5 62.5q33 0 64 -10v-54q0 -18 -12.5 -30.5t-29.5 -12.5h-43v-42q0 -9 -6.5 -15.5t-14.5 -6.5h-43v-42h128q9 0 15 -6.5t6 -15.5v-64h22q14 0 25 -8t15 -21q45 49 45 115q0 7 -2 21zM192 1v42q-18 0 -30.5 12.5t-12.5 29.5v22 l-102 102q-4 -20 -4 -38q0 -65 42.5 -113.5t106.5 -56.5z" />
+<glyph unicode="&#xf173;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM192 23v41q-18 0 -30.5 12.5t-12.5 30.5v21l-102 102q-4 -20 -4 -38q0 -65 42.5 -113t106.5 -56zM339 77q45 49 45 115q0 53 -29.5 96t-77.5 62v-9 q0 -17 -12.5 -29.5t-29.5 -12.5h-43v-43q0 -9 -6.5 -15t-14.5 -6h-43v-43h128q9 0 15 -6.5t6 -14.5v-64h22q14 0 25 -8.5t15 -21.5z" />
+<glyph unicode="&#xf174;" horiz-adv-x="469" d="M85 167l150 -82l149 82v-86l-149 -81l-150 81v86zM235 384l234 -128v-171h-42v148l-192 -105l-235 128z" />
+<glyph unicode="&#xf175;" d="M171 21h-107v171h-64l213 192l214 -192h-64v-171h-107v128h-85v-128z" />
+<glyph unicode="&#xf176;" horiz-adv-x="384" d="M384 341v-42l-43 -128l43 -128v-43h-384v43l43 128l-43 128v42h271l31 86l50 -19l-24 -67h56zM277 149v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64z" />
+<glyph unicode="&#xf177;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM320 149v86h-85v85h-86v-85h-85v-86h85v-85h86v85h85z" />
+<glyph unicode="&#xf178;" horiz-adv-x="469" d="M128 171q-27 0 -45.5 18.5t-18.5 45t18.5 45.5t45.5 19t45.5 -19t18.5 -45.5t-18.5 -45t-45.5 -18.5zM384 299q35 0 60 -25t25 -61v-192h-42v64h-384v-64h-43v320h43v-192h170v150h171z" />
+<glyph unicode="&#xf179;" horiz-adv-x="256" d="M0 405h256v-128v0l-85 -85l85 -85v-1v-127h-256v127v1l85 85l-85 85v0v128zM213 96l-85 85l-85 -85h170zM43 288h170v75h-170v-75z" />
+<glyph unicode="&#xf17a;" horiz-adv-x="256" d="M0 405h256v-128v0l-85 -85l85 -85v-1v-127h-256v127v1l85 85l-85 85v0v128zM213 96l-85 85l-85 -85v-75h170v75zM128 203l85 85v75h-170v-75z" />
+<glyph unicode="&#xf17b;" horiz-adv-x="256" d="M0 405h256v-128v0l-85 -85l85 -85v-1v-127h-256v127v1l85 85l-85 85v0v128z" />
+<glyph unicode="&#xf17c;" horiz-adv-x="469" d="M75 213v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43zM128 224v32h96v-32h-32v-96h-32v96h-32zM245 224v32h96v-32h-32v-96h-32v96h-32zM437 256q13 0 22.5 -9.5t9.5 -22.5v-21q0 -13 -9.5 -22.5t-22.5 -9.5h-42v-43h-32v128h74zM437 203v21h-42v-21h42z" />
+<glyph unicode="&#xf17d;" horiz-adv-x="469" d="M469 64q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h384q17 0 29.5 -12.5t12.5 -30.5v-256zM160 181l-75 -96h299l-96 128l-75 -96z" />
+<glyph unicode="&#xf17e;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM341 43v298h-298v-298h298zM234 186l75 -101h-234l58 76l42 -51z" />
+<glyph unicode="&#xf17f;" horiz-adv-x="384" d="M384 43q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298q18 0 30.5 -12.5t12.5 -30.5v-298zM117 160l-74 -96h298l-96 128l-74 -96z" />
+<glyph unicode="&#xf180;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-299q-17 0 -29.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t29.5 12.5h299zM341 128v213h-299v-213h86q0 -27 18.5 -45.5t45.5 -18.5t45.5 18.5t18.5 45.5h85zM277 235l-85 -86l-85 86h42v64h86v-64 h42z" />
+<glyph unicode="&#xf181;" horiz-adv-x="384" d="M377 3l7 -8l-27 -27l-58 58q-46 -38 -107 -38q-71 0 -121 50q-46 46 -49.5 112t37.5 116l-59 59l27 27l59 -59l30 -30l76 -76l134 -134zM192 30v103l-102 102q-26 -34 -26 -77q0 -53 38 -90q37 -38 90 -38zM192 339l-49 -48l-30 30l79 79l121 -121q38 -39 47 -92.5 t-13 -99.5l-155 154v98z" />
+<glyph unicode="&#xf182;" horiz-adv-x="341" d="M291 279q50 -50 50 -121t-50 -120.5t-120.5 -49.5t-120.5 49.5t-50 120.5t50 121l121 121zM171 30v309l-91 -90q-37 -38 -37 -91t37 -90q37 -38 91 -38z" />
+<glyph unicode="&#xf183;" horiz-adv-x="469" d="M249 235h220v-86h-42v-85h-86v85h-92q-14 -37 -47 -61t-74 -24q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q41 0 74 -24t47 -61zM128 149q18 0 30.5 12.5t12.5 30.5t-12.5 30.5t-30.5 12.5t-30.5 -12.5t-12.5 -30.5t12.5 -30.5t30.5 -12.5z" />
+<glyph unicode="&#xf184;" horiz-adv-x="405" d="M312 323l93 -131l-93 -131q-13 -18 -35 -18h-234q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h234q22 0 35 -18zM277 85l76 107l-76 107h-234v-214h234z" />
+<glyph unicode="&#xf185;" horiz-adv-x="405" d="M312 323l93 -131l-93 -131q-13 -18 -35 -18h-234q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h234q22 0 35 -18z" />
+<glyph unicode="&#xf186;" d="M414 201q13 -13 13 -30.5t-13 -29.5l-149 -150q-13 -12 -30.5 -12t-29.5 12l-192 192q-13 13 -13 30v150q0 17 12.5 29.5t30.5 12.5h149q18 0 30 -12zM74.5 299q13.5 0 23 9t9.5 22.5t-9.5 23t-23 9.5t-22.5 -9.5t-9 -23t9 -22.5t22.5 -9zM326 122q15 16 15 38 t-15.5 37.5t-37.5 15.5t-38 -15l-15 -16l-16 16q-15 15 -38 15q-22 0 -37.5 -15.5t-15.5 -37.5t16 -38l91 -91z" />
+<glyph unicode="&#xf187;" d="M414 201q13 -13 13 -30.5t-13 -29.5l-149 -150q-13 -12 -30.5 -12t-29.5 12l-192 192q-13 13 -13 30v150q0 17 12.5 29.5t30.5 12.5h149q18 0 30 -12zM74.5 299q13.5 0 23 9t9.5 22.5t-9.5 23t-23 9.5t-22.5 -9.5t-9 -23t9 -22.5t22.5 -9z" />
+<glyph unicode="&#xf188;" horiz-adv-x="446" d="M26 29q-16 7 -22.5 23t-0.5 32l52 125v-192zM442 108q7 -16 0 -32.5t-23 -23.5l-157 -65q-8 -3 -16 -3q-29 0 -39 26l-106 256q-4 8 -3 17q0 28 26 38l157 65q8 3 17 3q28 0 39 -26zM140.5 261q8.5 0 15 6.5t6.5 15t-6.5 15t-15 6.5t-15 -6.5t-6.5 -15t6.5 -15t15 -6.5z M98 27v135l73 -178h-31q-17 0 -29.5 12.5t-12.5 30.5z" />
+<glyph unicode="&#xf189;" horiz-adv-x="469" d="M54 52l39 39l30 -30l-39 -39zM213 -31v63h43v-63h-43zM64 224v-43h-64v43h64zM299 313q29 -17 46.5 -46t17.5 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 35 17 64t47 46v103h128v-103zM405 224h64v-43h-64v43zM346 61l30 29l39 -38l-30 -30z" />
+<glyph unicode="&#xf18a;" horiz-adv-x="469" d="M277 320l192 -256h-469l128 171l96 -128l34 25l-61 81z" />
+<glyph unicode="&#xf18b;" d="M380 128l-31 31l26 19l30 -30zM370 229l-51 -40l-168 168l62 48l192 -149zM27 427l400 -400l-27 -27l-81 81l-106 -82l-192 149l35 27l157 -123l76 59l-31 30l-45 -34l-157 122l-35 27l69 54l-90 90z" />
+<glyph unicode="&#xf18c;" horiz-adv-x="384" d="M192 52l157 123l35 -27l-192 -149l-192 149l35 27zM192 107l-157 122l-35 27l192 149l192 -149l-35 -27z" />
+<glyph unicode="&#xf18d;" horiz-adv-x="384" d="M192 202q81 75 192 75v-234q-110 0 -192 -76q-81 76 -192 76v234q111 0 192 -75zM192 277q-27 0 -45.5 19t-18.5 45.5t18.5 45t45.5 18.5t45.5 -18.5t18.5 -45t-18.5 -45.5t-45.5 -19z" />
+<glyph unicode="&#xf18e;" d="M41 192q0 -27 19 -46.5t47 -19.5h85v-41h-85q-44 0 -75.5 31.5t-31.5 75.5t31.5 75.5t75.5 31.5h85v-41h-85q-28 0 -47 -19.5t-19 -46.5zM128 171v42h171v-42h-171zM320 299q44 0 75.5 -31.5t31.5 -75.5t-31.5 -75.5t-75.5 -31.5h-85v41h85q27 0 46.5 19.5t19.5 46.5 t-19.5 46.5t-46.5 19.5h-85v41h85z" />
+<glyph unicode="&#xf18f;" horiz-adv-x="341" d="M170.5 85q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5zM299 277q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h194v43 q0 27 -19.5 46.5t-47 19.5t-46.5 -19.5t-19 -46.5h-41q0 44 31.5 75.5t75.5 31.5t75 -31.5t31 -75.5v-43h22zM299 21v214h-256v-214h256z" />
+<glyph unicode="&#xf190;" horiz-adv-x="341" d="M299 277q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75 -31.5t31 -75.5v-43h22zM170.5 386q-27.5 0 -46.5 -19.5t-19 -46.5h2v-43h130v43 q0 27 -19.5 46.5t-47 19.5zM299 21v214h-256v-214h256zM170.5 85q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5z" />
+<glyph unicode="&#xf191;" horiz-adv-x="341" d="M299 277q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75 -31.5t31 -75.5v-43h22zM170.5 85q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5 t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5zM237 277v43q0 27 -19.5 46.5t-47 19.5t-46.5 -19.5t-19 -46.5v-43h132z" />
+<glyph unicode="&#xf192;" horiz-adv-x="512" d="M149 277l-85 -85l85 -85v-64l-149 149l149 149v-64zM277 256q54 -8 96.5 -30.5t69.5 -55.5t43.5 -69.5t25.5 -79.5q-78 109 -235 109v-87l-149 149l149 149v-85z" />
+<glyph unicode="&#xf193;" horiz-adv-x="384" d="M149 256q54 -8 96.5 -30.5t69.5 -55.5t43.5 -69.5t25.5 -79.5q-78 109 -235 109v-87l-149 149l149 149v-85z" />
+<glyph unicode="&#xf194;" horiz-adv-x="448" d="M0 0v149l320 43l-320 43v149l448 -192z" />
+<glyph unicode="&#xf195;" horiz-adv-x="384" d="M341 320q18 0 30.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h42q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5h42zM192 384q-27 0 -45.5 -18.5t-18.5 -45.5h128q0 27 -18.5 45.5 t-45.5 18.5zM192 171q44 0 75.5 31t31.5 75h-43q0 -26 -18.5 -45t-45.5 -19t-45.5 19t-18.5 45h-43q0 -44 31.5 -75t75.5 -31z" />
+<glyph unicode="&#xf196;" horiz-adv-x="384" d="M373 384q11 0 11 -11v-322q0 -8 -8 -10l-120 -41l-128 45l-114 -44l-3 -1q-11 0 -11 11v322q0 8 8 10l120 41l128 -45l114 44zM256 43v253l-128 45v-253z" />
+<glyph unicode="&#xf197;" horiz-adv-x="432" d="M0 48v48h432v-48h-432zM0 168v48h432v-48h-432zM0 336h432v-48h-432v48z" />
+<glyph unicode="&#xf198;" d="M192 85v22h-43v42h86v22h-64q-9 0 -15.5 6t-6.5 15v64q0 9 6.5 15t15.5 6h21v22h43v-22h42v-42h-85v-22h64q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-21v-22h-43zM384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5 t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM384 64v256h-341v-256h341z" />
+<glyph unicode="&#xf199;" horiz-adv-x="339" d="M180 301q-18 0 -32 -6l-32 31q15 8 32 12v46h64v-47q32 -8 49.5 -30t19.5 -51h-48q-2 45 -53 45zM27 361l312 -312l-27 -27l-48 48q-19 -18 -52 -24v-46h-64v46q-33 7 -55 28t-23 54h46q5 -45 64 -45q38 0 52 20l-75 74q-84 25 -84 84l-73 73z" />
+<glyph unicode="&#xf19a;" horiz-adv-x="217" d="M117 215q46 -11 73 -32t27 -61q0 -32 -20.5 -51t-53.5 -25v-46h-64v46q-34 7 -55.5 28t-23.5 54h47q4 -45 64 -45q31 0 44 12t13 26q0 17 -13.5 30t-50.5 22q-100 24 -100 88q0 29 21 49.5t54 27.5v46h64v-47q32 -8 49.5 -30t18.5 -51h-47q-2 45 -53 45q-27 0 -42.5 -11 t-15.5 -29q0 -15 14 -25.5t50 -20.5z" />
+<glyph unicode="&#xf19b;" horiz-adv-x="85" d="M42.5 277q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5zM42.5 235q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM42.5 107q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5 t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5z" />
+<glyph unicode="&#xf19c;" horiz-adv-x="341" d="M42.5 235q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM298.5 235q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM170.5 235q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5 t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5z" />
+<glyph unicode="&#xf19d;" horiz-adv-x="341" d="M299 384h42v-384h-42v43h-43v-43h-171v43h-42v-43h-43v384h43v-43h42v43h171v-43h43v43zM85 85v43h-42v-43h42zM85 171v42h-42v-42h42zM85 256v43h-42v-43h42zM299 85v43h-43v-43h43zM299 171v42h-43v-42h43zM299 256v43h-43v-43h43z" />
+<glyph unicode="&#xf19e;" d="M341 363h86v-299q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h21l43 -86h64l-43 86h43l42 -86h64l-42 86h42l43 -86h64z" />
+<glyph unicode="&#xf19f;" horiz-adv-x="430" d="M430 252q0 -57 -37.5 -99t-93.5 -49v-83h64v-42h-342v106h-21v86q0 8 6.5 14.5t14.5 6.5h64q9 0 15.5 -6.5t6.5 -14.5v-86h-22v-64h171v84q-53 9 -88.5 50.5t-35.5 96.5q0 62 43.5 106t105.5 44t105.5 -44t43.5 -106zM53.5 213q-13.5 0 -23 9.5t-9.5 23t9.5 22.5t23 9 t22.5 -9t9 -22.5t-9 -23t-22.5 -9.5z" />
+<glyph unicode="&#xf1a0;" horiz-adv-x="302" d="M171 104v-83h128v-42h-299v42h128v84q-53 9 -88.5 50.5t-35.5 96.5q0 62 43.5 106t105.5 44t105.5 -44t43.5 -106q0 -57 -37.5 -99t-93.5 -49z" />
+<glyph unicode="&#xf1a1;" horiz-adv-x="320" d="M160 405l160 -390l-15 -15l-145 64l-145 -64l-15 15z" />
+<glyph unicode="&#xf1a2;" horiz-adv-x="384" d="M341 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-85v43h85v213h-298v-213h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h298zM192 235l85 -86h-64v-128h-42v128h-64z" />
+<glyph unicode="&#xf1a3;" horiz-adv-x="384" d="M341 43v149h43v-149q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h149v-43h-149v-298h298zM235 384h149v-149h-43v76l-209 -209l-30 30l209 209h-76v43z" />
+<glyph unicode="&#xf1a4;" horiz-adv-x="384" d="M192 384q80 0 136 -50t56 -121q0 -44 -31.5 -75t-75.5 -31h-37q-14 0 -23 -9.5t-9 -22.5q0 -12 8 -21q8 -10 8 -22q0 -13 -9.5 -22.5t-22.5 -9.5q-80 0 -136 56t-56 136t56 136t136 56zM74.5 192q13.5 0 23 9.5t9.5 22.5t-9.5 22.5t-23 9.5t-22.5 -9.5t-9 -22.5t9 -22.5 t22.5 -9.5zM138.5 277q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM245.5 277q13.5 0 22.5 9.5t9 23t-9 22.5t-22.5 9t-23 -9t-9.5 -22.5t9.5 -23t23 -9.5zM309.5 192q13.5 0 22.5 9.5t9 22.5t-9 22.5t-22.5 9.5t-23 -9.5t-9.5 -22.5 t9.5 -22.5t23 -9.5z" />
+<glyph unicode="&#xf1a5;" horiz-adv-x="277" d="M149 384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-64v-128h-85v384h149zM154 213q17 0 29.5 12.5t12.5 30.5t-12.5 30.5t-29.5 12.5h-69v-86h69z" />
+<glyph unicode="&#xf1a6;" horiz-adv-x="384" d="M341 405q18 0 30.5 -12.5t12.5 -29.5v-299q0 -18 -12.5 -30.5t-30.5 -12.5h-85l-64 -64l-64 64h-85q-18 0 -30.5 12.5t-12.5 30.5v299q0 17 12.5 29.5t30.5 12.5h298zM192 335q-24 0 -41 -17t-17 -41t17 -40.5t41 -16.5t41 16.5t17 40.5t-17 41t-41 17zM320 107v19 q0 20 -23.5 35.5t-52.5 23t-52 7.5t-52 -7.5t-52.5 -23t-23.5 -35.5v-19h256z" />
+<glyph unicode="&#xf1a7;" horiz-adv-x="384" d="M341 405q18 0 30.5 -12.5t12.5 -29.5v-299q0 -18 -12.5 -30.5t-30.5 -12.5h-85l-64 -64l-64 64h-85q-18 0 -30.5 12.5t-12.5 30.5v299q0 17 12.5 29.5t30.5 12.5h298zM232 173l88 40l-88 40l-40 88l-40 -88l-88 -40l88 -40l40 -88z" />
+<glyph unicode="&#xf1a8;" horiz-adv-x="299" d="M277 277q0 -27 -13 -61t-32 -63t-37.5 -55t-31.5 -40l-14 -15q-5 5 -13.5 15t-30.5 39t-39 56.5t-31 62t-14 61.5q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM107 277.5q0 -17.5 12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30zM0 21 h299v-42h-299v42z" />
+<glyph unicode="&#xf1a9;" horiz-adv-x="384" d="M341 405q18 0 30.5 -12.5t12.5 -29.5v-299q0 -18 -12.5 -30.5t-30.5 -12.5h-85l-64 -64l-64 64h-85q-18 0 -30.5 12.5t-12.5 30.5v299q0 17 12.5 29.5t30.5 12.5h298zM213 64v43h-42v-43h42zM257 229q20 20 20 48q0 36 -25 61t-60 25t-60 -25t-25 -61h42q0 18 12.5 30.5 t30.5 12.5t30.5 -12.5t12.5 -30.5q0 -17 -13 -30l-26 -27q-25 -25 -25 -60v-11h42q0 22 6 34.5t19 26.5z" />
+<glyph unicode="&#xf1aa;" horiz-adv-x="384" d="M213 309q-23 0 -39 -18l-68 68q44 46 107 46q62 0 106 -43.5t44 -105.5q0 -48 -37 -117l-77 78q18 16 18 39q0 22 -16 37.5t-38 15.5zM307 105l77 -78l-27 -27l-72 71q-16 -23 -34 -46.5t-28 -34.5l-10 -11q-6 6 -16 18t-35.5 46.5t-45.5 67t-36 73.5t-16 72q0 16 4 33 l-68 68l27 27l178 -178l3 -3z" />
+<glyph unicode="&#xf1ab;" horiz-adv-x="299" d="M149 405q62 0 106 -43.5t44 -105.5q0 -31 -15.5 -71.5t-37.5 -75t-44 -65t-37 -48.5l-16 -17q-6 6 -16 18t-35.5 46.5t-45.5 67t-36 73.5t-16 72q0 62 43.5 105.5t105.5 43.5zM149 203q22 0 38 15.5t16 37.5t-16 37.5t-38 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5 t37.5 -15.5z" />
+<glyph unicode="&#xf1ac;" horiz-adv-x="384" d="M192 405q56 0 105.5 -22.5t86.5 -62.5l-192 -341l-192 341q36 40 86 62.5t106 22.5zM85 298.5q0 -17.5 12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30zM191.5 128q17.5 0 30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30 t30 -12.5z" />
+<glyph unicode="&#xf1ad;" horiz-adv-x="428" d="M336 192l86 -85q6 -7 6 -15.5t-6 -14.5l-93 -93q-6 -6 -15 -6t-15 6l-85 85l-85 -85q-6 -6 -15 -6t-15 6l-93 93q-6 6 -6 14.5t6 15.5l85 85l-85 84q-6 7 -6 15.5t6 15.5l93 92q6 6 14.5 6t15.5 -6l85 -85l85 85q6 6 15 6t15 -6l92 -92q7 -7 7 -15.5t-7 -15.5zM214 256 q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15.5 6.5t6.5 15t-6.5 15t-15.5 6.5zM114 214l77 78l-77 77l-78 -78zM171.5 171q8.5 0 15 6t6.5 15t-6.5 15t-15 6t-15 -6t-6.5 -15t6.5 -15t15 -6zM214 128q9 0 15.5 6.5t6.5 15t-6.5 15t-15.5 6.5t-15 -6.5t-6 -15t6 -15t15 -6.5z M257 213q-9 0 -15 -6t-6 -15t6 -15t15 -6t15 6t6 15t-6 15t-15 6zM314 14l77 78l-77 77l-78 -78z" />
+<glyph unicode="&#xf1ae;" horiz-adv-x="341" d="M64 -64v43h43v-43h-43zM149 -64v43h43v-43h-43zM192 405v-213h-43v213h43zM268 353q34 -23 53.5 -60t19.5 -80q0 -70 -50 -120t-120.5 -50t-120.5 50t-50 120q0 43 19.5 80t53.5 60l31 -30q-28 -18 -44.5 -47t-16.5 -63q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5 q0 34 -17 63t-45 46zM235 -64v43h42v-43h-42z" />
+<glyph unicode="&#xf1af;" horiz-adv-x="384" d="M213 384v-213h-42v213h42zM316 338q68 -58 68 -146q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 88 68 146l30 -30q-55 -45 -55 -116q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5t43.5 105.5q0 71 -55 115z" />
+<glyph unicode="&#xf1b0;" d="M363 277q26 0 45 -18.5t19 -45.5v-128h-86v-85h-256v85h-85v128q0 27 18.5 45.5t45.5 18.5h299zM299 43v106h-171v-106h171zM362.5 192q8.5 0 15 6.5t6.5 15t-6.5 15t-15 6.5t-15 -6.5t-6.5 -15t6.5 -15t15 -6.5zM341 384v-85h-256v85h256z" />
+<glyph unicode="&#xf1b1;" horiz-adv-x="448" d="M395 213q22 0 37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5h-32v-86q0 -17 -12.5 -29.5t-30.5 -12.5h-81v32q0 24 -17 40.5t-41 16.5t-40.5 -16.5t-16.5 -40.5v-32h-81q-18 0 -30.5 12.5t-12.5 29.5v81h32q24 0 41 17t17 41t-17 41t-41 17h-32v81q0 17 12.5 29.5 t30.5 12.5h85v32q0 22 15.5 38t37.5 16t38 -16t16 -38v-32h85q18 0 30.5 -12.5t12.5 -29.5v-86h32z" />
+<glyph unicode="&#xf1b2;" horiz-adv-x="299" d="M21 85l43 86h-64v128h128v-128l-43 -86h-64zM192 85l43 86h-64v128h128v-128l-43 -86h-64z" />
+<glyph unicode="&#xf1b3;" horiz-adv-x="341" d="M0 117v224q0 27 12.5 45t38 26.5t53 11.5t67 3t67 -3t53 -11.5t38 -26.5t12.5 -45v-224q0 -31 -21.5 -52.5t-52.5 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5zM170.5 85q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5 t30 -12.5zM299 235v106h-256v-106h256z" />
+<glyph unicode="&#xf1b4;" horiz-adv-x="384" d="M320 85v43h-256v-43h256zM320 171v42h-256v-42h256zM320 256v43h-256v-43h256zM0 -21v426l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32v-426l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32z " />
+<glyph unicode="&#xf1b5;" horiz-adv-x="469" d="M384 277l85 -85h-64q0 -71 -50 -121t-120 -50q-49 0 -91 27l31 31q27 -15 60 -15q53 0 90.5 37.5t37.5 90.5h-64zM107 192h64l-86 -85l-85 85h64q0 71 50 121t121 50q49 0 91 -27l-32 -31q-27 15 -59 15q-53 0 -90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf1b6;" horiz-adv-x="384" d="M0 192q0 59 36 105t92 60v-44q-38 -14 -61.5 -47t-23.5 -74q0 -53 37 -90l48 47v-128h-128l50 51q-50 50 -50 120zM171 85v43h42v-43h-42zM384 363l-50 -51q50 -50 50 -120q0 -59 -36 -105t-92 -60v44q38 14 61.5 47t23.5 74q0 53 -37 90l-48 -47v128h128zM171 171v128 h42v-128h-42z" />
+<glyph unicode="&#xf1b7;" horiz-adv-x="366" d="M152 313q-5 -2 -16 -8l-31 32q22 14 47 20v-44zM0 333l27 27l335 -336l-27 -27l-50 50q-22 -14 -48 -20v44q7 3 17 8l-173 172q-14 -28 -14 -59q0 -53 38 -90l47 47v-128h-128l51 51q-51 50 -51 120q0 49 26 90zM366 363l-51 -51q51 -50 51 -120q0 -49 -26 -90l-32 31 q15 28 15 59q0 53 -38 90l-47 -47v128h128z" />
+<glyph unicode="&#xf1b8;" horiz-adv-x="341" d="M171 363q70 0 120 -50t50 -121q0 -49 -26 -91l-31 31q15 28 15 60q0 53 -37.5 90.5t-90.5 37.5v-64l-86 85l86 86v-64zM171 64v64l85 -85l-85 -86v64q-71 0 -121 50t-50 121q0 49 26 91l32 -31q-15 -28 -15 -60q0 -53 37.5 -90.5t90.5 -37.5z" />
+<glyph unicode="&#xf1b9;" horiz-adv-x="341" d="M171 320q-53 0 -90.5 -37.5t-37.5 -90.5q0 -32 15 -60l-32 -31q-26 42 -26 91q0 71 50 121t121 50v64l85 -86l-85 -85v64zM315 283q26 -42 26 -91q0 -71 -50 -121t-120 -50v-64l-86 86l86 85v-64q53 0 90.5 37.5t37.5 90.5q0 31 -15 60z" />
+<glyph unicode="&#xf1ba;" horiz-adv-x="363" d="M299 363h64v-171h-171v-192q0 -9 -6.5 -15t-14.5 -6h-43q-9 0 -15 6t-6 15v235h213v85h-21v-21q0 -9 -6.5 -15.5t-15.5 -6.5h-256q-8 0 -14.5 6.5t-6.5 15.5v85q0 9 6.5 15t14.5 6h256q9 0 15.5 -6t6.5 -15v-21z" />
+<glyph unicode="&#xf1bb;" horiz-adv-x="469" d="M427 320q17 0 29.5 -12.5t12.5 -30.5v-170q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v170q0 18 12.5 30.5t30.5 12.5h384zM427 107v170h-43v-85h-43v85h-42v-85h-43v85h-43v-85h-42v85h-43v-85h-43v85h-42v-170h384z" />
+<glyph unicode="&#xf1bc;" d="M163 285l264 -264v-21h-64l-150 149l-50 -50q8 -17 8 -35q0 -35 -25 -60t-60.5 -25t-60.5 25t-25 60t25 60t60 25q19 0 35 -7l51 50l-51 50q-16 -7 -35 -7q-35 0 -60 25t-25 60t25 60t60.5 25t60.5 -25t25 -60q0 -18 -8 -35zM85.5 277q17.5 0 30 12.5t12.5 30.5 t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5zM85.5 21q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5zM213.5 181q10.5 0 10.5 11t-10.5 11t-10.5 -11t10.5 -11zM363 384h64v-21l-150 -150l-42 43z" />
+<glyph unicode="&#xf1bd;" horiz-adv-x="484" d="M475 176q9 -10 9 -23t-9 -23l-136 -135q-9 -10 -22.5 -10t-22.5 10l-257 256q-9 9 -9 22.5t9 22.5l136 136q9 9 22.5 9t22.5 -9l53 -52l-31 -30l-44 44l-121 -120l241 -242l121 121l-47 47l30 30zM159 11l29 28l81 -81l-14 -1q-100 0 -173.5 68t-81.5 167h32 q6 -60 40 -108t87 -73zM320 256q-9 0 -15 6.5t-6 14.5v86q0 8 6 14.5t15 6.5v11q0 22 15.5 37.5t37.5 15.5t38 -15.5t16 -37.5v-11q8 0 14.5 -6.5t6.5 -14.5v-86q0 -8 -6.5 -14.5t-14.5 -6.5h-107zM337 395v-11h73v11q0 15 -11 25.5t-26 10.5t-25.5 -10.5t-10.5 -25.5z" />
+<glyph unicode="&#xf1be;" horiz-adv-x="510" d="M351 394l-29 -28l-81 81l14 1q100 0 173.5 -68t81.5 -167h-32q-6 60 -40.5 108t-86.5 73zM217 411l257 -257q9 -9 9 -22.5t-9 -22.5l-136 -136q-9 -9 -22.5 -9t-22.5 9l-257 257q-9 9 -9 22.5t9 22.5l136 136q9 9 22.5 9t22.5 -9zM315 -4l136 136l-256 256l-136 -136z M159 -10l29 28l81 -81l-14 -1q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73z" />
+<glyph unicode="&#xf1bf;" d="M320 149l107 -106l-32 -32l-107 107v16l-6 6q-39 -33 -90 -33q-38 0 -70 19l31 31q19 -8 39 -8q40 0 68 28.5t28 68t-28 67.5t-68 28t-68 -28t-28 -68h75l-89 -85l-82 85h54q0 57 40.5 98t97.5 41q58 0 98.5 -40.5t40.5 -98.5q0 -51 -34 -90l6 -6h17z" />
+<glyph unicode="&#xf1c0;" horiz-adv-x="341" d="M341 30l-81 82q17 27 17 59q0 44 -31 75t-75 31t-75.5 -31t-31.5 -75t31.5 -75.5t75.5 -31.5q31 0 59 18l94 -95q-12 -8 -25 -8h-257q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h170l128 -128v-247zM107 170.5q0 26.5 18.5 45.5t45 19t45.5 -19t19 -45.5 t-19 -45t-45.5 -18.5t-45 18.5t-18.5 45z" />
+<glyph unicode="&#xf1c1;" d="M203 256q22 0 37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-38 16t-16 38t16 37.5t38 15.5zM384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM316 60l30 30l-62 62 q15 23 15 51q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t67 -28q28 0 51 15z" />
+<glyph unicode="&#xf1c2;" horiz-adv-x="373" d="M149 320q-38 0 -67.5 -24.5t-36.5 -60.5h-43q8 54 49.5 91t97.5 37q62 0 106 -44l44 44v-128h-128l54 54q-32 31 -76 31zM270 125l103 -104l-32 -31l-103 103q-40 -29 -89 -29q-62 0 -105 44l-44 -44v128h128l-54 -54q31 -31 75 -31q39 0 68 24t37 61h43q-5 -37 -27 -67z " />
+<glyph unicode="&#xf1c3;" horiz-adv-x="373" d="M267 149l106 -106l-32 -32l-106 106v17l-6 6q-39 -33 -90 -33q-58 0 -98.5 40.5t-40.5 98t40.5 98t98 40.5t98 -40.5t40.5 -98.5q0 -51 -33 -90l6 -6h17zM139 149q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28z" />
+<glyph unicode="&#xf1c4;" d="M43 64v64h341v-128h-64v64h-213v-64h-64v64zM363 235h64v-64h-64v64zM0 235h64v-64h-64v64zM320 171h-213v170q0 18 12.5 30.5t29.5 12.5h128q18 0 30.5 -12.5t12.5 -30.5v-170z" />
+<glyph unicode="&#xf1c5;" horiz-adv-x="384" d="M192 235q18 0 30.5 -12.5t12.5 -30.5t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5t12.5 30.5t30.5 12.5zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM304 192 q0 7 -1 15l32 24q4 5 1 10l-30 52q-3 5 -9 3l-37 -15q-12 9 -25 15l-6 39q-1 6 -7 6h-60q-6 0 -7 -6l-6 -40q-14 -5 -25 -14l-37 15q-6 2 -9 -4l-30 -51q-3 -6 1 -10l32 -24q-1 -8 -1 -15t1 -15l-32 -24q-4 -5 -1 -10l30 -52q3 -5 9 -3l37 15q12 -9 25 -15l6 -39q1 -6 7 -6 h60q6 0 7 6l6 40q14 5 25 14l37 -15q6 -2 9 4l30 51q3 6 -1 10l-32 24q1 8 1 15z" />
+<glyph unicode="&#xf1c6;" horiz-adv-x="415" d="M366 171l45 -35q7 -6 3 -14l-43 -74q-4 -8 -13 -4l-53 21q-18 -13 -36 -21l-8 -56q-1 -9 -11 -9h-85q-9 0 -11 9l-8 56q-19 8 -36 21l-53 -21q-9 -3 -13 4l-43 74q-4 8 3 14l45 35q-1 12 -1 21t1 21l-45 35q-7 6 -3 14l43 74q5 8 13 4l53 -21q18 13 36 21l8 56q2 9 11 9 h85q10 0 11 -9l8 -56q19 -8 36 -21l53 21q9 3 13 -4l43 -74q4 -8 -3 -14l-45 -35q2 -12 2 -21t-2 -21zM207.5 117q30.5 0 52.5 22t22 53t-22 53t-52.5 22t-52.5 -22t-22 -53t22 -53t52.5 -22z" />
+<glyph unicode="&#xf1c7;" horiz-adv-x="384" d="M192 427l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128zM149 85l171 171l-30 30l-141 -140l-55 55l-30 -30z" />
+<glyph unicode="&#xf1c8;" horiz-adv-x="384" d="M192 427l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128zM192 192v188l-149 -66v-122h149v-191q59 19 100 72t49 119h-149z" />
+<glyph unicode="&#xf1c9;" horiz-adv-x="469" d="M346 256h102q9 0 15 -6.5t6 -14.5v-6l-54 -198q-4 -13 -15.5 -22t-26.5 -9h-277q-15 0 -26 9t-15 22l-54 198q-1 2 -1 6q0 8 6.5 14.5t14.5 6.5h103l93 140q6 9 17.5 9t17.5 -9zM171 256h128l-64 94zM234.5 85q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5 t-12.5 -30.5t12.5 -30.5t30 -12.5z" />
+<glyph unicode="&#xf1ca;" horiz-adv-x="430" d="M213 256v64h-64v43h64v64h43v-64h64v-43h-64v-64h-43zM128 64q18 0 30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5t-30 12.5t-12 30t12 30t30 12.5zM341.5 64q17.5 0 30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5zM132 133q0 -5 5 -5h247v-43 h-256q-18 0 -30.5 12.5t-12.5 30.5q0 11 6 20l28 53l-76 162h-43v42h70l20 -42l20 -43l48 -101l3 -6h149l59 107l24 43l37 -21l-82 -149q-12 -22 -38 -22h-159l-19 -35v-3z" />
+<glyph unicode="&#xf1cb;" d="M128 64q18 0 30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5t-30 12.5t-12 30t12 30t30 12.5zM0 405h70l20 -42h315q9 0 15.5 -6.5t6.5 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -38 -22h-159l-19 -35v-3q0 -5 5 -5h247v-43h-256q-18 0 -30.5 12.5t-12.5 30.5q0 11 6 20l28 53 l-76 162h-43v42zM341.5 64q17.5 0 30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5z" />
+<glyph unicode="&#xf1cc;" horiz-adv-x="384" d="M151 115l55 56h-206v42h206l-55 56l30 30l107 -107l-107 -107zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v85h43v-85h298v298h-298v-85h-43v85q0 18 12.5 30.5t30.5 12.5h298z" />
+<glyph unicode="&#xf1cd;" horiz-adv-x="384" d="M0 320h128v-43h-128v43zM0 64v43h384v-43h-384zM0 213h256v-42h-256v42z" />
+<glyph unicode="&#xf1ce;" horiz-adv-x="384" d="M0 64v43h128v-43h-128zM0 320h384v-43h-384v43zM0 171v42h256v-42h-256z" />
+<glyph unicode="&#xf1cf;" horiz-adv-x="417" d="M282 364h-147l74 73zM135 20h147l-73 -73zM70 157h81l-41 111zM93 315h35l93 -246h-38l-20 53h-106l-19 -53h-38zM285 103h132v-34h-184v28l128 183h-127v35h179v-27z" />
+<glyph unicode="&#xf1d0;" horiz-adv-x="417" d="M282 364h-147l74 73zM135 20h147l-73 -73zM266 157h81l-40 111zM290 315h34l93 -246h-38l-19 53h-106l-20 -53h-38zM52 103h132v-34h-184v28l128 183h-127v35h179v-27z" />
+<glyph unicode="&#xf1d1;" horiz-adv-x="438" d="M213 107l-24 64h-121l-23 -64h-45l109 277h40l109 -277h-45zM85 213h88l-44 118zM408 201l30 -30l-202 -203l-109 109l30 30l79 -79z" />
+<glyph unicode="&#xf1d2;" d="M0 21v86h427v-86h-427zM43 85v-42h42v42h-42zM0 363h427v-86h-427v86zM85 299v42h-42v-42h42zM0 149v86h427v-86h-427zM43 213v-42h42v42h-42z" />
+<glyph unicode="&#xf1d3;" d="M363 299h64v-278h-171v86h-85v-86h-171v278h64v64h299v-64zM192 235v64h-64v-22h43v-21h-43v-64h64v21h-43v22h43zM299 192v107h-22v-43h-21v43h-21v-64h42v-43h22z" />
+<glyph unicode="&#xf1d4;" horiz-adv-x="384" d="M363 363v-43h-342v43h342zM384 149h-21v-128h-43v128h-85v-128h-214v128h-21v43l21 107h342l21 -107v-43zM192 64v85h-128v-85h128z" />
+<glyph unicode="&#xf1d5;" horiz-adv-x="341" d="M170.5 405q39.5 0 67 -3t53 -11.5t38 -26t12.5 -44.5v-203q0 -31 -21.5 -52.5t-52.5 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 27 12.5 44.5t38 26t53 11.5t67 3zM74.5 85q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23 t22.5 -9.5zM149 213v107h-106v-107h106zM266.5 85q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM299 213v107h-107v-107h107z" />
+<glyph unicode="&#xf1d6;" horiz-adv-x="469" d="M123 345l-30 -30l-39 38l30 30zM64 224v-43h-64v43h64zM256 436v-63h-43v63h43zM415 353l-38 -38l-30 30l38 38zM346 61l30 29l39 -38l-30 -30zM405 224h64v-43h-64v43zM235 331q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5 t90.5 37.5zM213 -31v63h43v-63h-43zM54 52l39 39l30 -30l-39 -39z" />
+<glyph unicode="&#xf1d7;" horiz-adv-x="469" d="M0 256v43h43v-43h-43zM0 171v42h43v-42h-43zM0 341q0 18 12.5 30.5t30.5 12.5v-43h-43zM171 0v43h42v-43h-42zM0 85v43h43v-43h-43zM43 0q-18 0 -30.5 12.5t-12.5 30.5h43v-43zM427 384q17 0 29.5 -12.5t12.5 -30.5v-85h-213v128h171zM427 85v43h42v-43h-42zM171 341v43 h42v-43h-42zM85 0v43h43v-43h-43zM85 341v43h43v-43h-43zM427 0v43h42q0 -18 -12.5 -30.5t-29.5 -12.5zM427 171v42h42v-42h-42zM256 0v43h43v-43h-43zM341 0v43h43v-43h-43z" />
+<glyph unicode="&#xf1d8;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 43v213h-171v85h-213v-298h384z" />
+<glyph unicode="&#xf1d9;" horiz-adv-x="512" d="M469 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-320q-21 0 -34 19l-115 173l115 173q13 19 34 19h320zM405 115l-76 77l76 77l-30 30l-76 -77l-77 77l-30 -30l77 -77l-77 -77l30 -30l77 77l76 -77z" />
+<glyph unicode="&#xf1da;" horiz-adv-x="512" d="M469 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-318q-23 0 -36 19l-115 173l115 173q13 19 34 19h320zM192 160q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM298.5 160q13.5 0 23 9.5t9.5 22.5 t-9.5 22.5t-23 9.5t-22.5 -9.5t-9 -22.5t9 -22.5t22.5 -9.5zM405.5 160q13.5 0 22.5 9.5t9 22.5t-9 22.5t-22.5 9.5t-23 -9.5t-9.5 -22.5t9.5 -22.5t23 -9.5z" />
+<glyph unicode="&#xf1db;" horiz-adv-x="512" d="M469 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-320q-21 0 -34 19l-115 173l115 173q13 19 34 19h320z" />
+<glyph unicode="&#xf1dc;" horiz-adv-x="469" d="M299 384q17 0 29.5 -12.5t12.5 -30.5v-213q0 -18 -12 -30l-141 -141l-22 23q-10 9 -10 22l1 7l20 98h-134q-18 0 -30.5 12.5t-12.5 29.5v2v41q0 8 3 16l64 150q11 26 40 26h192zM384 384h85v-256h-85v256z" />
+<glyph unicode="&#xf1dd;" horiz-adv-x="512" d="M256 320v-27q0 -6 -2 -11l-49 -113q-8 -20 -29 -20h-144q-13 0 -22.5 9.5t-9.5 22.5v139q0 13 9 23l106 105l17 -17q7 -7 7 -17l-1 -5l-14 -68h111q8 0 14.5 -6t6.5 -15zM480 235q13 0 22.5 -9.5t9.5 -22.5v-139q0 -13 -9 -23l-106 -105l-17 17q-7 7 -7 17l1 5l14 68 h-111q-8 0 -14.5 6t-6.5 15v27q0 6 2 11l49 113q8 20 29 20h144z" />
+<glyph unicode="&#xf1de;" horiz-adv-x="469" d="M0 0v256h85v-256h-85zM469 235v-2v-41q0 -8 -3 -16l-64 -150q-11 -26 -39 -26h-192q-18 0 -30.5 12.5t-12.5 30.5v213q0 18 13 30l140 141l23 -23q9 -9 9 -22l-1 -7l-20 -98h135q17 0 29.5 -12.5t12.5 -29.5z" />
+<glyph unicode="&#xf1df;" d="M384 192q0 -18 12.5 -30.5t30.5 -12.5v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v85q18 0 30.5 12.5t12.5 30.5t-12.5 30.5t-30.5 12.5v85q0 18 12.5 30.5t30.5 12.5h341q18 0 30.5 -12.5t12.5 -30.5v-85q-18 0 -30.5 -12.5t-12.5 -30.5z M290 90l-24 87l71 58l-91 5l-33 84l-33 -84l-90 -5l70 -58l-23 -87l76 49z" />
+<glyph unicode="&#xf1e0;" horiz-adv-x="469" d="M298.5 363q70.5 0 120.5 -50t50 -121t-50 -121t-120.5 -50t-120.5 50t-50 121t50 121t120.5 50zM299 64q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM43 192q0 -41 23.5 -74t61.5 -47v-44q-56 14 -92 60t-36 105 t36 105t92 60v-44q-38 -14 -61.5 -47t-23.5 -74z" />
+<glyph unicode="&#xf1e1;" horiz-adv-x="469" d="M235 192q0 48 34.5 82.5t82.5 34.5t82.5 -34.5t34.5 -82.5h-234zM235 192q0 -48 -35 -82.5t-83 -34.5t-82.5 34.5t-34.5 82.5h235zM235 192q-48 0 -83 34.5t-35 82.5t35 83t83 35v-235zM235 192q48 0 82.5 -34.5t34.5 -82.5t-34.5 -83t-82.5 -35v235z" />
+<glyph unicode="&#xf1e2;" horiz-adv-x="341" d="M341 235q0 -30 -18 -52.5t-46 -30.5v-24h64q0 -29 -18 -52t-46 -30v-25q0 -8 -6 -14.5t-15 -6.5h-171q-8 0 -14.5 6.5t-6.5 14.5v25q-28 7 -46 30t-18 52h64v24q-28 8 -46 30.5t-18 52.5h64v24q-28 7 -46 30t-18 52h64v22q0 8 6.5 14.5t14.5 6.5h171q9 0 15 -6.5t6 -14.5 v-22h64q0 -29 -18 -52t-46 -30v-24h64zM170.5 43q17.5 0 30 12.5t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30t30 -12.5zM170.5 149q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5zM170.5 256q17.5 0 30 12.5 t12.5 30t-12.5 30t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30t30 -12.5z" />
+<glyph unicode="&#xf1e3;" horiz-adv-x="469" d="M253 127l-16 -44l-66 66l-107 -106l-30 30l108 107q-40 44 -63 97h42q20 -39 50 -71q45 50 67 114h-238v43h149v42h43v-42h149v-43h-62q-24 -78 -79 -139l-1 -1zM373 235l96 -256h-42l-24 64h-102l-24 -64h-42l96 256h42zM317 85h70l-35 93z" />
+<glyph unicode="&#xf1e4;" d="M213 78l137 219h-273zM213 -2l-213 341h427z" />
+<glyph unicode="&#xf1e5;" d="M213 282l-136 -218h273zM213 363l214 -342h-427z" />
+<glyph unicode="&#xf1e6;" horiz-adv-x="469" d="M405 277l64 -85v-107h-42q0 -26 -19 -45t-45.5 -19t-45 19t-18.5 45h-128q0 -26 -19 -45t-45.5 -19t-45 19t-18.5 45h-43v235q0 18 12.5 30.5t30.5 12.5h298v-86h64zM106.5 53q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM395 245h-54v-53 h95zM362.5 53q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5z" />
+<glyph unicode="&#xf1e7;" d="M420 207q7 -6 7 -15t-7 -15l-192 -192q-6 -6 -15 -6t-15 6l-192 192q-6 6 -6 15t6 15l192 192q6 6 15 6t15 -6zM256 139l75 74l-75 75v-53h-107q-9 0 -15 -6.5t-6 -15.5v-85h43v64h85v-53z" />
+<glyph unicode="&#xf1e8;" d="M43 363v-150h-43v150q0 17 12.5 29.5t30.5 12.5h149v-42h-149zM171 171l63 -79l43 57l64 -85h-256zM320 266.5q0 -13.5 -9.5 -22.5t-22.5 -9t-22.5 9t-9.5 22.5t9.5 23t22.5 9.5t22.5 -9.5t9.5 -23zM384 405q18 0 30.5 -12.5t12.5 -29.5v-150h-43v150h-149v42h149z M384 21v150h43v-150q0 -17 -12.5 -29.5t-30.5 -12.5h-149v42h149zM43 171v-150h149v-42h-149q-18 0 -30.5 12.5t-12.5 29.5v150h43z" />
+<glyph unicode="&#xf1e9;" horiz-adv-x="341" d="M110 89l121 121q25 -25 25 -60.5t-25 -60.5t-60.5 -25t-60.5 25zM299 405q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h256zM128 363q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5 t6 15t-6 15t-15 6.5zM64 363q-9 0 -15 -6.5t-6 -15t6 -15t15 -6.5t15 6.5t6 15t-6 15t-15 6.5zM171 21q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5z" />
+<glyph unicode="&#xf1ea;" horiz-adv-x="341" d="M0 363h341v-342h-341v342zM43 277v-213h256v213h-256z" />
+<glyph unicode="&#xf1eb;" horiz-adv-x="341" d="M341 21h-341v86h341v-86z" />
+<glyph unicode="&#xf1ec;" horiz-adv-x="341" d="M0 277h85v86h256v-256h-85v-86h-256v256zM256 277v-128h43v171h-171v-43h128zM43 192v-128h170v128h-170z" />
+<glyph unicode="&#xf1ed;" horiz-adv-x="470" d="M464 43q6 -5 6 -14.5t-8 -15.5l-49 -49q-7 -7 -15.5 -7t-14.5 7l-194 194q-37 -15 -77.5 -6.5t-70.5 38.5q-31 32 -39 75.5t12 82.5l94 -92l64 64l-92 92q38 18 82 10.5t76 -38.5q30 -30 38.5 -70.5t-6.5 -76.5z" />
+<glyph unicode="&#xf1ee;" horiz-adv-x="373" d="M267 149l106 -106l-32 -32l-106 106v17l-6 6q-39 -33 -90 -33q-58 0 -98.5 40.5t-40.5 98t40.5 98t98 40.5t98 -40.5t40.5 -98.5q0 -51 -33 -90l6 -6h17zM139 149q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM192 235h-43v-43h-21v43h-43v21h43v43 h21v-43h43v-21z" />
+<glyph unicode="&#xf1ef;" horiz-adv-x="373" d="M267 149l106 -106l-32 -32l-106 106v17l-6 6q-39 -33 -90 -33q-58 0 -98.5 40.5t-40.5 98t40.5 98t98 40.5t98 -40.5t40.5 -98.5q0 -51 -33 -90l6 -6h17zM139 149q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM85 256h107v-21h-107v21z" />
+<glyph unicode="&#xf1f0;" d="M192 128h43v-43h-43v43zM192 299h43v-128h-43v128zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf1f1;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM235 85v43h-43v-43h43zM235 171v128h-43v-128h43z" />
+<glyph unicode="&#xf1f2;" horiz-adv-x="384" d="M272 384l112 -112v-160l-112 -112h-160l-112 112v160l112 112h160zM192 79q12 0 20 8t8 19.5t-8 19.5t-20 8t-20 -8t-8 -19.5t8 -19.5t20 -8zM213 171v128h-42v-128h42z" />
+<glyph unicode="&#xf1f3;" horiz-adv-x="469" d="M469 192l-52 -59l8 -79l-77 -17l-41 -68l-72 31l-73 -31l-40 67l-77 18l7 79l-52 59l52 60l-7 78l77 17l40 68l73 -31l72 31l41 -68l77 -17l-8 -79zM256 85v43h-43v-43h43zM256 171v128h-43v-128h43z" />
+<glyph unicode="&#xf1f4;" horiz-adv-x="469" d="M0 0l235 405l234 -405h-469zM256 64v43h-43v-43h43zM256 149v86h-43v-86h43z" />
+<glyph unicode="&#xf1f5;" d="M192 64v43h43v-43h-43zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM213.5 320q35.5 0 60.5 -25t25 -60 q0 -18 -10 -32.5t-22 -23t-22 -22t-10 -29.5h-43q0 23 10 39.5t22 24t22 18.5t10 25q0 17 -12.5 29.5t-30 12.5t-30 -12.5t-12.5 -29.5h-43q0 35 25 60t60.5 25z" />
+<glyph unicode="&#xf1f6;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM235 43v42h-43v-42h43zM279 208q20 20 20 48q0 35 -25 60t-60.5 25t-60.5 -25t-25 -60h43q0 18 12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-13 -30l-26 -27 q-25 -25 -25 -60v-11h43q0 22 5.5 34.5t19.5 25.5z" />
+<glyph unicode="&#xf1f7;" d="M192 85v128h43v-128h-43zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM192 256v43h43v-43h-43z" />
+<glyph unicode="&#xf1f8;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM235 85v128h-43v-128h43zM235 256v43h-43v-43h43z" />
+<glyph unicode="&#xf1f9;" horiz-adv-x="447" d="M118 372q-33 -24 -53 -60t-22 -77h-43q2 50 25.5 94t62.5 73zM404 235q-2 41 -22.5 77t-53.5 60l31 30q39 -29 62 -73t26 -94h-43zM362 224v-117l43 -43v-21h-363v21l43 43v117q0 49 30 86.5t76 48.5v14q0 14 9.5 23t23 9t22.5 -9t9 -23v-14q47 -11 77 -48.5t30 -86.5z M223 -21q-17 0 -29.5 12.5t-12.5 29.5h85q0 -8 -3 -16q-9 -21 -31 -25q-4 -1 -9 -1z" />
+<glyph unicode="&#xf1fa;" horiz-adv-x="384" d="M150 0h84q0 -18 -12 -30.5t-30 -12.5t-30 12.5t-12 30.5zM339 89l45 -45v-23h-384v23l45 45v124q0 52 32 91.5t81 51.5v15q0 14 10 24t24 10t24 -10t10 -24v-15q49 -12 81 -51.5t32 -91.5v-124zM277 170v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64z" />
+<glyph unicode="&#xf1fb;" horiz-adv-x="363" d="M181.5 -21q-17.5 0 -30 12.5t-12.5 29.5h85q0 -17 -12.5 -29.5t-30 -12.5zM320 107l43 -43v-21h-363v21l43 43v117q0 49 30 86.5t76 48.5v14q0 14 9.5 23t23 9t22.5 -9t9 -23v-14q47 -11 77 -48.5t30 -86.5v-117zM277 85v139q0 40 -28 68t-68 28t-68 -28t-28 -68v-139 h192z" />
+<glyph unicode="&#xf1fc;" horiz-adv-x="384" d="M181.5 -21q-17.5 0 -30 12.5t-12.5 29.5h85q0 -17 -12.5 -29.5t-30 -12.5zM320 224v-79l-202 202q17 8 31 12v14q0 14 9.5 23t23 9t22.5 -9t9 -23v-14q47 -11 77 -48.5t30 -86.5zM314 43h-314v21l43 43v117q0 38 19 71l-62 62l27 27l357 -357l-27 -27z" />
+<glyph unicode="&#xf1fd;" horiz-adv-x="363" d="M181.5 -21q-17.5 0 -30 12.5t-12.5 29.5h85q0 -17 -12.5 -29.5t-30 -12.5zM320 107l43 -43v-21h-363v21l43 43v117q0 49 30 86.5t76 48.5v14q0 14 9.5 23t23 9t22.5 -9t9 -23v-14q47 -11 77 -48.5t30 -86.5v-117zM235 239v38h-107v-38h60l-60 -73v-38h107v38h-60z" />
+<glyph unicode="&#xf1fe;" horiz-adv-x="363" d="M181.5 -21q-17.5 0 -30 12.5t-12.5 29.5h85q0 -17 -12.5 -29.5t-30 -12.5zM320 107l43 -43v-21h-363v21l43 43v117q0 49 30 86.5t76 48.5v14q0 14 9.5 23t23 9t22.5 -9t9 -23v-14q47 -11 77 -48.5t30 -86.5v-117z" />
+<glyph unicode="&#xf1ff;" horiz-adv-x="469" d="M298.5 192q-35.5 0 -60.5 25t-25 60.5t25 60.5t60.5 25t60.5 -25t25 -60.5t-25 -60.5t-60.5 -25zM107 235h64v-43h-64v-64h-43v64h-64v43h64v64h43v-64zM298.5 149q31.5 0 69.5 -9t69.5 -29.5t31.5 -46.5v-43h-341v43q0 26 31.5 46.5t69.5 29.5t69.5 9z" />
+<glyph unicode="&#xf200;" horiz-adv-x="512" d="M448 277l-64 -42l-64 42v22l64 -43l64 43v-22zM469 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-426q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h426zM170.5 320q-26.5 0 -45 -18.5t-18.5 -45.5t18.5 -45.5t45 -18.5t45.5 18.5 t19 45.5t-19 45.5t-45.5 18.5zM299 64v21q0 20 -24 36t-52.5 23t-52 7t-52 -7t-52 -23t-23.5 -36v-21h256zM469 192v128h-170v-128h170z" />
+<glyph unicode="&#xf201;" horiz-adv-x="384" d="M192 187q-20 0 -34 14t-14 34t14 34t34 14t34 -14t14 -34t-14 -34t-34 -14zM288 101v-16h-192v16q0 22 33 35t63 13t63 -13t33 -35zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5 t30.5 12.5h298zM341 43v298h-298v-298h298z" />
+<glyph unicode="&#xf202;" horiz-adv-x="512" d="M469 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-426q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h426zM170.5 320q-26.5 0 -45 -18.5t-18.5 -45.5t18.5 -45.5t45 -18.5t45.5 18.5t19 45.5t-19 45.5t-45.5 18.5zM299 64v21 q0 20 -24 36t-52.5 23t-52 7t-52 -7t-52 -23t-23.5 -36v-21h256zM381 149q-8 22 -8 43t8 43h35l32 42l-42 43q-44 -33 -59 -85q-6 -22 -6 -43t6 -43q15 -52 59 -85l42 43l-32 42h-35z" />
+<glyph unicode="&#xf203;" horiz-adv-x="384" d="M0 341q0 18 12.5 30.5t30.5 12.5h298q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298zM256 256q0 27 -18.5 45.5t-45.5 18.5t-45.5 -18.5t-18.5 -45.5t18.5 -45.5t45.5 -18.5t45.5 18.5t18.5 45.5zM64 85v-21h256 v21q0 20 -23.5 36t-52.5 23t-52 7t-52 -7t-52.5 -23t-23.5 -36z" />
+<glyph unicode="&#xf204;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43h43v-43h21zM192 320q-27 0 -45.5 -18.5t-18.5 -45.5t18.5 -45.5t45.5 -18.5t45.5 18.5t18.5 45.5 t-18.5 45.5t-45.5 18.5zM320 64v21q0 20 -23.5 36t-52.5 23t-52 7t-52 -7t-52.5 -23t-23.5 -36v-21h256z" />
+<glyph unicode="&#xf205;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 341q-26.5 0 -45.5 -18.5t-19 -45t19 -45.5t45.5 -19t45 19t18.5 45.5t-18.5 45t-45 18.5zM213.5 38q39.5 0 73 18.5t54.5 50.5q0 20 -23.5 35.5 t-52 23t-52 7.5t-52 -7.5t-52 -23t-24.5 -35.5q21 -32 55 -50.5t73.5 -18.5z" />
+<glyph unicode="&#xf206;" horiz-adv-x="341" d="M170.5 322q-18.5 0 -31.5 -13t-13 -31.5t13 -31.5t31.5 -13t31.5 13t13 31.5t-13 31.5t-31.5 13zM171 130q-44 0 -87 -16.5t-43 -28.5v-23h260v23q0 12 -43 28.5t-87 16.5zM170.5 363q35.5 0 60.5 -25t25 -60.5t-25 -60.5t-60.5 -25t-60.5 25t-25 60.5t25 60.5t60.5 25z M170.5 171q31.5 0 69.5 -9t69.5 -29.5t31.5 -47.5v-64h-341v64q0 27 31.5 47.5t69.5 29.5t69.5 9z" />
+<glyph unicode="&#xf207;" horiz-adv-x="341" d="M170.5 192q-35.5 0 -60.5 25t-25 60.5t25 60.5t60.5 25t60.5 -25t25 -60.5t-25 -60.5t-60.5 -25zM170.5 149q31.5 0 69.5 -9t69.5 -29.5t31.5 -46.5v-43h-341v43q0 26 31.5 46.5t69.5 29.5t69.5 9z" />
+<glyph unicode="&#xf208;" horiz-adv-x="512" d="M171 235v-43h-64v-64h-43v64h-64v43h64v64h43v-64h64zM384 213q-10 0 -19 3q19 28 19 61q0 34 -19 61q9 3 19 3q27 0 45.5 -18.5t18.5 -45t-18.5 -45.5t-45.5 -19zM277.5 213q-26.5 0 -45.5 19t-19 45.5t19 45t45.5 18.5t45 -18.5t18.5 -45t-18.5 -45.5t-45 -19zM419 167 q37 -6 65 -22t28 -38v-43h-64v43q0 34 -29 60zM277 171q40 0 84 -18t44 -46v-43h-256v43q0 28 44 46t84 18z" />
+<glyph unicode="&#xf209;" d="M309 192q-22 0 -37.5 15.5t-15.5 37.5t15.5 38t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5zM149.5 213q-26.5 0 -45.5 19t-19 45.5t19 45t45.5 18.5t45 -18.5t18.5 -45t-18.5 -45.5t-45 -19zM309.5 149q36.5 0 77 -16t40.5 -42v-48h-235v48q0 26 40.5 42t77 16z M149 171q22 0 51 -6q-51 -28 -51 -74v-48h-149v53q0 23 27.5 41t61 26t60.5 8z" />
+<glyph unicode="&#xf20a;" d="M384 448v-43h-341v43h341zM43 -64v43h341v-43h-341zM384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM213 304q-20 0 -34 -14t-14 -34t14 -34t34 -14t34 14t14 34t-14 34 t-34 14zM320 85v32q0 24 -36.5 39t-70 15t-70 -15t-36.5 -39v-32h213z" />
+<glyph unicode="&#xf20b;" horiz-adv-x="469" d="M427 341h42v-298h-42v298zM341 43v298h43v-298h-43zM277 341q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-256q-8 0 -14.5 6t-6.5 15v256q0 9 6.5 15t14.5 6h256zM149 283q-20 0 -34 -14t-14 -34t14 -34t34 -14t34 14t14 34t-14 34t-34 14zM245 85v16q0 22 -33 35 t-63 13t-63 -13t-33 -35v-16h192z" />
+<glyph unicode="&#xf20c;" horiz-adv-x="469" d="M331 171q25 0 56 -7.5t56.5 -24t25.5 -38.5v-58h-469v58q0 22 25.5 38.5t56.5 24t57 7.5q50 0 96 -22q46 22 96 22zM245 75v26q0 10 -35 24t-71.5 14t-71.5 -14t-35 -24v-26h213zM437 75v26q0 10 -35 24t-71 14q-32 0 -65 -12q11 -12 11 -26v-26h160zM139 192 q-31 0 -53 22t-22 53t22 52.5t53 21.5t52.5 -21.5t21.5 -52.5t-21.5 -53t-52.5 -22zM138.5 309q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5zM331 192q-31 0 -53 22t-22 53t22 52.5t53 21.5t52.5 -21.5t21.5 -52.5t-21.5 -53 t-52.5 -22zM330.5 309q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5z" />
+<glyph unicode="&#xf20d;" horiz-adv-x="469" d="M320 213q-27 0 -45.5 19t-18.5 45.5t18.5 45t45.5 18.5t45.5 -18.5t18.5 -45t-18.5 -45.5t-45.5 -19zM149.5 213q-26.5 0 -45.5 19t-19 45.5t19 45t45.5 18.5t45 -18.5t18.5 -45t-18.5 -45.5t-45 -19zM149.5 171q27.5 0 60.5 -8t61 -26t28 -41v-53h-299v53q0 23 27.5 41 t61 26t61 8zM320 171q28 0 61 -8t60.5 -26t27.5 -41v-53h-128v53q0 43 -42 74q13 1 21 1z" />
+<glyph unicode="&#xf20e;" d="M149 197q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-18.5 8t-7.5 19t7.5 18.5t18.5 7.5zM277 197q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-18.5 8t-7.5 19t7.5 18.5t18.5 7.5zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5 t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121q0 24 -7 48q-24 -5 -48 -5q-53 0 -99 24t-75 66q-33 -80 -111 -115q-1 -10 -1 -18q0 -71 50 -121t120.5 -50z" />
+<glyph unicode="&#xf20f;" horiz-adv-x="192" d="M128 -21h-64v128h-64l54 162q4 14 15.5 22t25.5 8h2q14 0 25 -8t16 -22l54 -162h-64v-128zM96 320q-18 0 -30.5 12.5t-12.5 30t12.5 30t30.5 12.5t30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5z" />
+<glyph unicode="&#xf210;" horiz-adv-x="149" d="M32 -21v160h-32v117q0 18 12.5 30.5t30.5 12.5h64q17 0 29.5 -12.5t12.5 -30.5v-117h-32v-160h-85zM74.5 320q-17.5 0 -30 12.5t-12.5 30t12.5 30t30 12.5t30 -12.5t12.5 -30t-12.5 -30t-30 -12.5z" />
+<glyph unicode="&#xf211;" horiz-adv-x="363" d="M32 -21v160h-32v117q0 18 12.5 30.5t30.5 12.5h64q17 0 29.5 -12.5t12.5 -30.5v-117h-32v-160h-85zM299 -21h-64v128h-64l54 162q4 14 15.5 22t24.5 8h3q14 0 25 -8t15 -22l55 -162h-64v-128zM74.5 320q-17.5 0 -30 12.5t-12.5 30t12.5 30t30 12.5t30 -12.5t12.5 -30 t-12.5 -30t-30 -12.5zM266.5 320q-17.5 0 -30 12.5t-12.5 30t12.5 30t30 12.5t30 -12.5t12.5 -30t-12.5 -30t-30 -12.5z" />
+<glyph unicode="&#xf212;" horiz-adv-x="384" d="M192 405q18 0 30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5t-30.5 12.5t-12.5 30t12.5 30t30.5 12.5zM384 256h-128v-277h-43v128h-42v-128h-43v277h-128v43h384v-43z" />
+<glyph unicode="&#xf213;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM288 213q-13 0 -22.5 9.5t-9.5 23t9.5 22.5t22.5 9t22.5 -9 t9.5 -22.5t-9.5 -23t-22.5 -9.5zM138.5 213q-13.5 0 -22.5 9.5t-9 23t9 22.5t22.5 9t23 -9t9.5 -22.5t-9.5 -23t-23 -9.5zM213.5 149q36.5 0 66 -20.5t42.5 -53.5h-218q13 33 43 53.5t66.5 20.5z" />
+<glyph unicode="&#xf214;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM288 213q-13 0 -22.5 9.5t-9.5 23t9.5 22.5t22.5 9t22.5 -9 t9.5 -22.5t-9.5 -23t-22.5 -9.5zM138.5 213q-13.5 0 -22.5 9.5t-9 23t9 22.5t22.5 9t23 -9t9.5 -22.5t-9.5 -23t-23 -9.5zM213.5 75q-36.5 0 -66.5 20.5t-43 53.5h218q-13 -33 -42.5 -53.5t-66 -20.5z" />
+<glyph unicode="&#xf215;" horiz-adv-x="343" d="M226.5 331q-17.5 0 -30.5 12.5t-13 30t13 30t30.5 12.5t30 -12.5t12.5 -30t-12.5 -30t-30 -12.5zM149 35l-149 29l9 43l104 -21l34 173l-38 -15v-73h-43v100l111 47q3 0 8.5 1t8.5 1q22 0 36 -21l22 -34q13 -23 37.5 -37t53.5 -14v-43q-71 0 -117 53l-13 -64l45 -42v-160 h-43v128l-44 42z" />
+<glyph unicode="&#xf216;" horiz-adv-x="277" d="M160 331q-18 0 -30.5 12.5t-12.5 30t12.5 30t30.5 12.5t30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5zM81 258l-60 -301h45l39 171l44 -43v-128h43v160l-45 43l13 64q46 -53 117 -53v42q-29 0 -53.5 14.5t-37.5 37.5l-22 34q-14 21 -36 21q-3 0 -8.5 -1t-8.5 -1l-111 -47 v-100h43v72l38 15v0z" />
+<glyph unicode="&#xf217;" horiz-adv-x="469" d="M171 107q-27 0 -45.5 18.5t-18.5 45.5q0 24 16.5 42.5t40.5 20.5h3q9 20 27.5 31.5t40.5 11.5q28 0 48.5 -18t24.5 -46h1q22 0 38 -15.5t16 -37.5t-16 -37.5t-38 -15.5h-138zM427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384 q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384v-300h384z" />
+<glyph unicode="&#xf218;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM309 107q22 0 38 15.5t16 37.5t-16 37.5t-38 15.5h-10q0 36 -25 61t-61 25q-29 0 -52 -18.5t-30 -46.5l-3 1q-27 0 -45.5 -19t-18.5 -45.5t18.5 -45 t45.5 -18.5h181z" />
+<glyph unicode="&#xf219;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q20 40 58 63.5t84 23.5q58 0 102 -37t55 -92zM213 85l141 141l-30 30l-111 -110l-44 44l-30 -30z" />
+<glyph unicode="&#xf21a;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q20 40 58 63.5t84 23.5q58 0 102 -37t55 -92zM363 171h-64v85h-86v-85h-64l107 -107z" />
+<glyph unicode="&#xf21b;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -55 -45 -87l-31 31q33 19 33 56q0 27 -18.5 45.5t-45.5 18.5h-32v11q0 48 -34 82.5t-83 34.5q-29 0 -54 -13l-32 31q40 25 86 25q58 0 102 -37t55 -92zM64 336l27 27l357 -357l-27 -27l-43 42h-250q-53 0 -90.5 37.5t-37.5 90.5 q0 52 35.5 89t87.5 39zM165 235h-37q-35 0 -60 -25t-25 -60.5t25 -60.5t60 -25h208z" />
+<glyph unicode="&#xf21c;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q21 40 59 63.5t83 23.5q58 0 102 -37t55 -92zM405 64q27 0 45.5 19t18.5 45t-18.5 45t-45.5 19h-32v11q0 48 -34.5 82.5t-82.5 34.5 q-58 0 -94 -47q41 -12 67.5 -46t26.5 -78h-43q0 36 -25 61t-60 25t-60 -25t-25 -60.5t25 -60.5t60 -25h277z" />
+<glyph unicode="&#xf21d;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q20 40 58 63.5t84 23.5q58 0 102 -37t55 -92zM405 64q27 0 45.5 18.5t18.5 45.5t-18.5 45.5t-45.5 18.5h-32v11q0 48 -34 82.5t-83 34.5 q-40 0 -71 -24t-42 -61h-15q-35 0 -60 -25t-25 -60.5t25 -60.5t60 -25h277z" />
+<glyph unicode="&#xf21e;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q20 40 58 63.5t84 23.5q58 0 102 -37t55 -92zM299 171h64l-107 106l-107 -106h64v-86h86v86z" />
+<glyph unicode="&#xf21f;" horiz-adv-x="512" d="M413 234q42 -3 70.5 -33.5t28.5 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 50 33 86t81 41q20 40 58 63.5t84 23.5q58 0 102 -37t55 -92z" />
+<glyph unicode="&#xf220;" horiz-adv-x="299" d="M299 256l-150 -149l-149 149h85v128h128v-128h86zM0 64h299v-43h-299v43z" />
+<glyph unicode="&#xf221;" horiz-adv-x="341" d="M213 405l128 -128v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-257q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h170zM256 107v42h-64v64h-43v-64h-64v-42h64v-64h43v64h64zM192 256h117l-117 117v-117z" />
+<glyph unicode="&#xf222;" horiz-adv-x="341" d="M213 405l128 -128v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-257q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h170zM256 64v43h-171v-43h171zM256 149v43h-171v-43h171zM192 256h117l-117 117v-117z" />
+<glyph unicode="&#xf223;" horiz-adv-x="341" d="M43 405h170l128 -128v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-257q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5zM192 256h117l-117 117v-117z" />
+<glyph unicode="&#xf224;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h128l42 -43h171zM384 64v213h-341v-213h341z" />
+<glyph unicode="&#xf225;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h128l42 -43h171zM277.5 256q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5zM363 85v22 q0 19 -29.5 30.5t-56 11.5t-56 -11.5t-29.5 -30.5v-22h171z" />
+<glyph unicode="&#xf226;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h128l42 -43h171zM247 64l-20 87l67 58l-89 8l-34 82l-35 -82l-89 -8l68 -58l-21 -87l77 45z" />
+<glyph unicode="&#xf227;" d="M43 363h128l42 -43h171q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5zM276 256l-25 -60l-65 -5l49 -43l-15 -63l56 33l56 -33l-14 63l49 43l-65 5z" />
+<glyph unicode="&#xf228;" d="M171 363l42 -43h171q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h128z" />
+<glyph unicode="&#xf229;" horiz-adv-x="299" d="M139 256h32v-128h-32v128zM85 256q10 0 16 -6.5t6 -14.5v-11h-75v-64h43v32h32v-43q0 -8 -6 -14.5t-16 -6.5h-64q-9 0 -15 6.5t-6 14.5v86q0 8 6 14.5t15 6.5h64zM299 224h-64v-21h42v-32h-42v-43h-32v128h96v-32z" />
+<glyph unicode="&#xf22a;" horiz-adv-x="299" d="M0 363h299v-43h-299v43zM0 149l149 150l150 -150h-86v-128h-128v128h-85z" />
+<glyph unicode="&#xf22b;" horiz-adv-x="384" d="M0 384h384v-384h-384v384zM171 43v128h-128v-128h128zM171 213v128h-128v-128h128zM341 43v128h-128v-128h128zM341 213v128h-128v-128h128z" />
+<glyph unicode="&#xf22c;" horiz-adv-x="384" d="M128 213v-42h-43v42h43zM213 128v-43h-42v43h42zM128 384v-43h-43v43h43zM213 213v-42h-42v42h42zM43 384v-43h-43v43h43zM213 299v-43h-42v43h42zM299 213v-42h-43v42h43zM213 384v-43h-42v43h42zM299 384v-43h-43v43h43zM341 171v42h43v-42h-43zM341 85v43h43v-43h-43z M43 299v-43h-43v43h43zM341 384h43v-43h-43v43zM341 256v43h43v-43h-43zM43 213v-42h-43v42h43zM0 0v43h384v-43h-384zM43 128v-43h-43v43h43z" />
+<glyph unicode="&#xf22d;" horiz-adv-x="384" d="M85 341v43h43v-43h-43zM85 171v42h43v-42h-43zM85 0v43h43v-43h-43zM171 85v43h42v-43h-42zM171 0v43h42v-43h-42zM0 0v43h43v-43h-43zM0 85v43h43v-43h-43zM0 171v42h43v-42h-43zM0 256v43h43v-43h-43zM0 341v43h43v-43h-43zM171 171v42h42v-42h-42zM341 85v43h43v-43 h-43zM341 171v42h43v-42h-43zM341 0v43h43v-43h-43zM341 256v43h43v-43h-43zM171 256v43h42v-43h-42zM341 384h43v-43h-43v43zM171 341v43h42v-43h-42zM256 0v43h43v-43h-43zM256 171v42h43v-42h-43zM256 341v43h43v-43h-43z" />
+<glyph unicode="&#xf22e;" horiz-adv-x="512" d="M379 299l-214 -214h-80v80l214 214zM442 362l-42 -42l-80 80l42 42q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM0 21h512v-85h-512v85z" />
+<glyph unicode="&#xf22f;" horiz-adv-x="384" d="M0 0v43h43v-43h-43zM43 299v-43h-43v43h43zM0 85v43h43v-43h-43zM85 0v43h43v-43h-43zM43 384v-43h-43v43h43zM128 384v-43h-43v43h43zM299 384v-43h-43v43h43zM213 299v-43h-42v43h42zM213 384v-43h-42v43h42zM341 85v43h43v-43h-43zM171 0v43h42v-43h-42zM0 171v42h384 v-42h-384zM341 384h43v-43h-43v43zM341 256v43h43v-43h-43zM171 85v43h42v-43h-42zM256 0v43h43v-43h-43zM341 0v43h43v-43h-43z" />
+<glyph unicode="&#xf230;" horiz-adv-x="384" d="M0 0v43h43v-43h-43zM85 0v43h43v-43h-43zM43 299v-43h-43v43h43zM0 85v43h43v-43h-43zM128 384v-43h-43v43h43zM43 384v-43h-43v43h43zM299 384v-43h-43v43h43zM341 256v43h43v-43h-43zM341 384h43v-43h-43v43zM256 0v43h43v-43h-43zM213 384v-171h171v-42h-171v-171h-42 v171h-171v42h171v171h42zM341 0v43h43v-43h-43zM341 85v43h43v-43h-43z" />
+<glyph unicode="&#xf231;" horiz-adv-x="384" d="M171 0v43h42v-43h-42zM171 85v43h42v-43h-42zM171 341v43h42v-43h-42zM171 256v43h42v-43h-42zM171 171v42h42v-42h-42zM85 0v43h43v-43h-43zM85 341v43h43v-43h-43zM85 171v42h43v-42h-43zM0 0v384h43v-384h-43zM341 256v43h43v-43h-43zM256 0v43h43v-43h-43zM341 85v43 h43v-43h-43zM341 384h43v-43h-43v43zM341 171v42h43v-42h-43zM341 0v43h43v-43h-43zM256 171v42h43v-42h-43zM256 341v43h43v-43h-43z" />
+<glyph unicode="&#xf232;" horiz-adv-x="384" d="M213 299v-43h-42v43h42zM213 213v-42h-42v42h42zM299 213v-42h-43v42h43zM0 384h384v-384h-384v384zM341 43v298h-298v-298h298zM213 128v-43h-42v43h42zM128 213v-42h-43v42h43z" />
+<glyph unicode="&#xf233;" horiz-adv-x="384" d="M85 0v43h43v-43h-43zM0 341v43h43v-43h-43zM85 341v43h43v-43h-43zM85 171v42h43v-42h-43zM0 0v43h43v-43h-43zM171 0v43h42v-43h-42zM0 171v42h43v-42h-43zM0 85v43h43v-43h-43zM0 256v43h43v-43h-43zM171 85v43h42v-43h-42zM256 171v42h43v-42h-43zM341 384h43v-384 h-43v384zM256 0v43h43v-43h-43zM256 341v43h43v-43h-43zM171 171v42h42v-42h-42zM171 341v43h42v-43h-42zM171 256v43h42v-43h-42z" />
+<glyph unicode="&#xf234;" horiz-adv-x="384" d="M256 0v43h43v-43h-43zM341 0v43h43v-43h-43zM85 0v43h43v-43h-43zM171 0v43h42v-43h-42zM341 85v43h43v-43h-43zM341 171v42h43v-42h-43zM0 384h384v-43h-341v-341h-43v384zM341 256v43h43v-43h-43z" />
+<glyph unicode="&#xf235;" horiz-adv-x="384" d="M85 0v43h43v-43h-43zM85 171v42h43v-42h-43zM171 171v42h42v-42h-42zM171 0v43h42v-43h-42zM0 85v43h43v-43h-43zM0 0v43h43v-43h-43zM0 171v42h43v-42h-43zM0 256v43h43v-43h-43zM171 85v43h42v-43h-42zM341 256v43h43v-43h-43zM341 171v42h43v-42h-43zM0 384h384v-43 h-384v43zM341 85v43h43v-43h-43zM256 0v43h43v-43h-43zM171 256v43h42v-43h-42zM341 0v43h43v-43h-43zM256 171v42h43v-42h-43z" />
+<glyph unicode="&#xf236;" horiz-adv-x="384" d="M0 256v43h43v-43h-43zM0 341v43h43v-43h-43zM85 0v43h43v-43h-43zM85 171v42h43v-42h-43zM0 171v42h43v-42h-43zM0 0v43h43v-43h-43zM0 85v43h43v-43h-43zM85 341v43h43v-43h-43zM341 85v43h43v-43h-43zM171 0v384h42v-384h-42zM341 0v43h43v-43h-43zM341 171v42h43v-42 h-43zM341 384h43v-43h-43v43zM341 256v43h43v-43h-43zM256 341v43h43v-43h-43zM256 0v43h43v-43h-43zM256 171v42h43v-42h-43z" />
+<glyph unicode="&#xf237;" horiz-adv-x="405" d="M299 427v-43h-256v-299h-43v299q0 18 12.5 30.5t30.5 12.5h256zM363 341q17 0 29.5 -12.5t12.5 -29.5v-299q0 -18 -12.5 -30.5t-29.5 -12.5h-235q-18 0 -30.5 12.5t-12.5 30.5v299q0 17 12.5 29.5t30.5 12.5h235zM363 0v299h-235v-299h235z" />
+<glyph unicode="&#xf238;" horiz-adv-x="469" d="M341 128v171h-170v42h170q18 0 30.5 -12.5t12.5 -29.5v-171h-43zM128 85h341v-42h-85v-86h-43v86h-213q-18 0 -30.5 12.5t-12.5 29.5v214h-85v42h85v86h43v-342z" />
+<glyph unicode="&#xf239;" horiz-adv-x="384" d="M85 128h214v-43h-214v43zM0 0v43h384v-43h-384zM0 171v42h384v-42h-384zM85 299h214v-43h-214v43zM0 384h384v-43h-384v43z" />
+<glyph unicode="&#xf23a;" horiz-adv-x="384" d="M0 0v43h384v-43h-384zM0 85v43h384v-43h-384zM0 171v42h384v-42h-384zM0 256v43h384v-43h-384zM0 384h384v-43h-384v43z" />
+<glyph unicode="&#xf23b;" horiz-adv-x="384" d="M256 128v-43h-256v43h256zM256 299v-43h-256v43h256zM0 171v42h384v-42h-384zM0 0v43h384v-43h-384zM0 384h384v-43h-384v43z" />
+<glyph unicode="&#xf23c;" horiz-adv-x="384" d="M0 0v43h384v-43h-384zM128 85v43h256v-43h-256zM0 171v42h384v-42h-384zM128 256v43h256v-43h-256zM0 384h384v-43h-384v43z" />
+<glyph unicode="&#xf23d;" horiz-adv-x="229" d="M183 218q21 -10 33.5 -29.5t12.5 -43.5q0 -34 -23 -57.5t-56 -23.5h-150v299h133q36 0 61 -25t25 -61q0 -35 -36 -59zM64 309v-64h64q13 0 22.5 9.5t9.5 23t-9.5 22.5t-22.5 9h-64zM139 117q13 0 22.5 9.5t9.5 23t-9.5 22.5t-22.5 9h-75v-64h75z" />
+<glyph unicode="&#xf23e;" horiz-adv-x="384" d="M43 171v42h298v-42h-298zM0 85v43h299v-43h-299zM85 299h299v-43h-299v43z" />
+<glyph unicode="&#xf23f;" horiz-adv-x="384" d="M27 341l6 -5l308 -309l-27 -27l-121 121l-33 -78h-64l53 123l-149 148zM85 341h299v-64h-124l-34 -80l-45 44l16 36h-52l-60 60v4z" />
+<glyph unicode="&#xf240;" horiz-adv-x="512" d="M353 257q10 -9 10 -22.5t-10 -22.5l-117 -117q-9 -10 -22.5 -10t-22.5 10l-118 117q-9 9 -9 22.5t9 22.5l110 110l-51 51l31 30zM111 235h205l-103 102zM405 203q43 -47 43 -75q0 -18 -12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5q0 13 10.5 31.5t21.5 30.5zM0 21h512v-85 h-512v85z" />
+<glyph unicode="&#xf241;" horiz-adv-x="338" d="M299 149q0 -14 -3 -28l-184 184q14 19 28.5 37.5t22.5 27.5l8 10q5 -6 13.5 -16.5t30.5 -40t39 -56.5t31 -60.5t14 -57.5zM280 83l58 -59l-27 -27l-56 56q-36 -32 -84 -32q-53 0 -90.5 37.5t-37.5 90.5q0 35 28 88l-71 71l27 28l154 -155z" />
+<glyph unicode="&#xf242;" horiz-adv-x="512" d="M0 21h512v-85h-512v85zM235 384h42l117 -299h-48l-23 64h-134l-24 -64h-48zM205 192h102l-51 135z" />
+<glyph unicode="&#xf243;" horiz-adv-x="384" d="M171 85v43h213v-43h-213zM0 192l85 85v-170zM0 0v43h384v-43h-384zM0 384h384v-43h-384v43zM171 256v43h213v-43h-213zM171 171v42h213v-42h-213z" />
+<glyph unicode="&#xf244;" horiz-adv-x="384" d="M0 0v43h384v-43h-384zM0 277l85 -85l-85 -85v170zM171 85v43h213v-43h-213zM0 384h384v-43h-384v43zM171 256v43h213v-43h-213zM171 171v42h213v-42h-213z" />
+<glyph unicode="&#xf245;" horiz-adv-x="256" d="M85 363h171v-64h-60l-72 -171h47v-64h-171v64h60l72 171h-47v64z" />
+<glyph unicode="&#xf246;" horiz-adv-x="437" d="M96 299v-214h53l-74 -74l-75 74h53v214h-53l75 74l74 -74h-53zM181 341h256v-42h-256v42zM181 43v42h256v-42h-256zM181 171v42h256v-42h-256z" />
+<glyph unicode="&#xf247;" horiz-adv-x="395" d="M32 224q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM32 352q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM32 92q12 0 20 -8t8 -20t-8 -20t-20 -8t-20 8t-8 20t8 20t20 8z M96 43v42h299v-42h-299zM96 171v42h299v-42h-299zM96 341h299v-42h-299v42z" />
+<glyph unicode="&#xf248;" horiz-adv-x="405" d="M0 85v22h64v-86h-64v22h43v10h-22v22h22v10h-43zM21 277v64h-21v22h43v-86h-22zM0 213v22h64v-20l-38 -44h38v-22h-64v20l38 44h-38zM107 341h298v-42h-298v42zM107 43v42h298v-42h-298zM107 171v42h298v-42h-298z" />
+<glyph unicode="&#xf249;" horiz-adv-x="341" d="M85 235q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-42v235h-43v-235h-43v107zM341 64l-85 -85v64h-256v42h256v64z" />
+<glyph unicode="&#xf24a;" horiz-adv-x="341" d="M128 235q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-43v235h-42v-235h-43v107zM85 85h256v-42h-256v-64l-85 85l85 85v-64z" />
+<glyph unicode="&#xf24b;" horiz-adv-x="405" d="M128 363h277v-64h-106v-256h-64v256h-107v64zM0 192v64h192v-64h-64v-149h-64v149h-64z" />
+<glyph unicode="&#xf24c;" horiz-adv-x="469" d="M105 235q-5 4 -7 8q-11 22 -11 47t13 47q8 18 30 36q19 14 49 24q26 8 62 8q40 0 66 -10q25 -6 49 -26q20 -16 30 -40q11 -25 11 -52h-86q0 11 -4 24q-3 13 -13 19q-10 10 -21 13q-17 4 -30 4t-30 -4q-8 -2 -21 -11q-10 -7 -13 -15q-4 -13 -4 -19q0 -22 21 -34 q14 -9 43 -19h-134zM469 192v-43h-91q1 -1 1.5 -2t1 -3t1.5 -3q8 -20 8 -47q0 -24 -10 -49q-8 -18 -30 -36q-21 -18 -47 -24q-26 -8 -62 -8q-15 0 -40 4q-13 2 -39 10q-13 7 -34 20q-14 8 -28 25q-13 17 -19 34q-6 20 -6 45h85q0 -21 6 -34q5 -8 17 -21q10 -10 26 -13 q21 -4 34 -4t30 4q3 2 10 5t9 6q10 6 13 15q4 12 4 19q0 13 -2 19q-3 11 -13 17q-17 12 -25 15q-2 1 -7.5 3t-7.5 3h-254v43h469z" />
+<glyph unicode="&#xf24d;" horiz-adv-x="384" d="M149 43v64h86v-64h-86zM43 363h298v-64h-106v-64h-86v64h-106v64zM0 149v43h384v-43h-384z" />
+<glyph unicode="&#xf24e;" horiz-adv-x="341" d="M213 85v-42h-213v42h213zM341 256v-43h-341v43h341zM0 128v43h341v-43h-341zM0 341h341v-42h-341v42z" />
+<glyph unicode="&#xf24f;" horiz-adv-x="299" d="M149 85q-53 0 -90.5 37.5t-37.5 90.5v171h54v-171q0 -31 21.5 -52.5t52.5 -21.5t53 21.5t22 52.5v171h53v-171q0 -53 -37.5 -90.5t-90.5 -37.5zM0 43h299v-43h-299v43z" />
+<glyph unicode="&#xf250;" horiz-adv-x="341" d="M256 171l-85 -86l-86 86h64v213h43v-213h64zM0 43h341v-43h-341v43z" />
+<glyph unicode="&#xf251;" horiz-adv-x="341" d="M85 43l86 85l85 -85h-64v-86h-43v86h-64zM256 341l-85 -85l-86 85h64v86h43v-86h64zM0 213h341v-42h-341v42z" />
+<glyph unicode="&#xf252;" horiz-adv-x="341" d="M85 213l86 86l85 -86h-64v-213h-43v213h-64zM0 384h341v-43h-341v43z" />
+<glyph unicode="&#xf253;" horiz-adv-x="436" d="M360 222l76 77v-192h-192l78 77q-48 40 -110 40q-56 0 -100.5 -33t-61.5 -84l-50 16q22 68 80.5 111t131.5 43q84 0 148 -55z" />
+<glyph unicode="&#xf254;" horiz-adv-x="384" d="M0 341q0 18 12.5 30.5t30.5 12.5v-43h-43zM0 171v42h43v-42h-43zM85 0v43h43v-43h-43zM0 256v43h43v-43h-43zM213 384v-43h-42v43h42zM341 384q18 0 30.5 -12.5t12.5 -30.5h-43v43zM43 0q-18 0 -30.5 12.5t-12.5 30.5h43v-43zM0 85v43h43v-43h-43zM128 384v-43h-43v43h43 zM171 0v43h42v-43h-42zM341 171v42h43v-42h-43zM341 0v43h43q0 -18 -12.5 -30.5t-30.5 -12.5zM341 256v43h43v-43h-43zM341 85v43h43v-43h-43zM256 0v43h43v-43h-43zM256 341v43h43v-43h-43zM85 85v214h214v-214h-214zM128 256v-128h128v128h-128z" />
+<glyph unicode="&#xf255;" horiz-adv-x="341" d="M299 256h42v-128h-341v128h43v-85h256v85z" />
+<glyph unicode="&#xf256;" horiz-adv-x="299" d="M0 85h299v-42h-299v42zM96 175l-19 -47h-45l101 235h32l102 -235h-45l-19 47h-107zM149 320l-40 -107h80z" />
+<glyph unicode="&#xf257;" d="M427 64h-86v-43h43l-64 -64l-64 64h43v43h-171q-18 0 -30.5 12.5t-12.5 30.5v170h-85v43h85v43h-42l64 64l64 -64h-43v-256h299v-43zM171 277v43h128q17 0 29.5 -12.5t12.5 -30.5v-128h-42v128h-128z" />
+<glyph unicode="&#xf258;" horiz-adv-x="437" d="M224 277q73 0 131.5 -43t81.5 -111l-51 -16q-17 51 -61.5 84t-100.5 33q-61 0 -109 -40l77 -77h-192v192l77 -77q64 55 147 55z" />
+<glyph unicode="&#xf259;" horiz-adv-x="363" d="M0 43v42h128v-42h-128zM341 341v-42h-341v42h341zM277 213q36 0 61 -25t25 -60t-25 -60t-61 -25h-42v-43l-64 64l64 64v-43h48q17 0 29.5 12.5t12.5 30.5t-12.5 30.5t-29.5 12.5h-283v42h277z" />
+<glyph unicode="&#xf25a;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM235 149v43h-43v-43h43zM235 235v85h-43v-85h43z" />
+<glyph unicode="&#xf25b;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-384l-86 85h-298q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h341zM341 149v43h-256v-43h256zM341 213v43h-256v-43h256zM341 277v43h-256v-43h256z" />
+<glyph unicode="&#xf25c;" d="M426 363l1 -384l-86 85h-298q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h341q18 0 30 -12.5t12 -29.5z" />
+<glyph unicode="&#xf25d;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM85 149h53l147 147q8 7 0 15l-38 38q-7 7 -15 0l-147 -147v-53zM341 149v43h-117l-43 -43h160z" />
+<glyph unicode="&#xf25e;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM64 149h299l-96 128l-75 -96l-53 64z" />
+<glyph unicode="&#xf25f;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM128 149v43h-43v-43h43zM128 213v43h-43v-43h43zM128 277v43h-43v-43h43zM277 149v43h-106v-43h106zM341 213v43h-170v-43h170zM341 277v43 h-170v-43h170z" />
+<glyph unicode="&#xf260;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM149 213v43h-42v-43h42zM235 213v43h-43v-43h43zM320 213v43h-43v-43h43z" />
+<glyph unicode="&#xf261;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM384 107v256h-341v-299l42 43h299z" />
+<glyph unicode="&#xf262;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM85 256v-43h256v43h-256zM256 149v43h-171v-43h171zM341 277v43h-256v-43h256z" />
+<glyph unicode="&#xf263;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM341 149v43h-256v-43h256zM341 213v43h-256v-43h256zM341 277v43h-256v-43h256z" />
+<glyph unicode="&#xf264;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341zM341 149v171l-85 -68v68h-171v-171h171v69z" />
+<glyph unicode="&#xf265;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-299l-85 -85v384q0 17 12.5 29.5t30.5 12.5h341z" />
+<glyph unicode="&#xf266;" d="M405 320q9 0 15.5 -6.5t6.5 -14.5v-320l-86 85h-234q-9 0 -15.5 6.5t-6.5 14.5v43h278v192h42zM320 192q0 -9 -6.5 -15t-14.5 -6h-214l-85 -86v299q0 9 6.5 15t14.5 6h278q8 0 14.5 -6t6.5 -15v-192z" />
+<glyph unicode="&#xf267;" horiz-adv-x="496" d="M375 299l-135 -136l-30 30l135 136zM466 329l30 -30l-256 -256l-119 119l30 30l89 -89zM0 162l30 30l119 -119l-30 -30z" />
+<glyph unicode="&#xf268;" d="M213 405q88 0 151 -62.5t63 -150.5t-63 -150.5t-151 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM320 64v43h-213v-43h213zM177 149l143 143l-30 30l-113 -113l-40 41l-30 -30z" />
+<glyph unicode="&#xf269;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM171 85l192 192l-30 31l-162 -162l-77 76l-30 -30z" />
+<glyph unicode="&#xf26a;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM149 85l192 192l-30 31l-162 -162l-76 76l-30 -30z" />
+<glyph unicode="&#xf26b;" horiz-adv-x="375" d="M119 102l227 227l29 -30l-256 -256l-119 119l30 30z" />
+<glyph unicode="&#xf26c;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf26d;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5z" />
+<glyph unicode="&#xf26e;" d="M213 405q88 0 151 -62.5t63 -150.5t-63 -150.5t-151 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM277 192q0 -27 -18.5 -45.5t-45 -18.5t-45.5 18.5t-19 45.5 t19 45.5t45.5 18.5t45 -18.5t18.5 -45.5z" />
+<glyph unicode="&#xf26f;" d="M213 299q44 0 75.5 -31.5t31.5 -75.5t-31.5 -75.5t-75.5 -31.5t-75 31.5t-31 75.5t31 75.5t75 31.5zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50 t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf270;" d="M107 213h213v-42h-213v42zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf271;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM320 171v42h-213v-42h213z" />
+<glyph unicode="&#xf272;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM299 171v42h-214v-42h214z" />
+<glyph unicode="&#xf273;" horiz-adv-x="299" d="M299 171h-299v42h299v-42z" />
+<glyph unicode="&#xf274;" horiz-adv-x="512" d="M341 277v-64h64v-42h-64v-64h-42v64h-64v42h64v64h42zM43 192q0 -44 23.5 -80.5t61.5 -54.5v-46q-56 20 -92 69.5t-36 111.5t36 111.5t92 69.5v-46q-38 -18 -61.5 -54.5t-23.5 -80.5zM320 384q79 0 135.5 -56.5t56.5 -135.5t-56.5 -135.5t-135.5 -56.5t-135.5 56.5 t-56.5 135.5t56.5 135.5t135.5 56.5zM320 43q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5z" />
+<glyph unicode="&#xf275;" d="M235 299v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43zM213 405q88 0 151 -62.5t63 -150.5t-63 -150.5t-151 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf276;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM320 171v42h-85v86h-43v-86h-85v-42h85v-86h43v86h85z" />
+<glyph unicode="&#xf277;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM299 171v42h-86v86h-42v-86h-86v-42h86v-86h42v86h86z" />
+<glyph unicode="&#xf278;" horiz-adv-x="299" d="M299 171h-128v-128h-43v128h-128v42h128v128h43v-128h128v-42z" />
+<glyph unicode="&#xf279;" horiz-adv-x="384" d="M341 341h-298v-298h298v298zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298z" />
+<glyph unicode="&#xf27a;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM304 64l-24 103l79 69l-105 9l-41 96l-41 -97l-105 -8l80 -69l-24 -103l90 54z" />
+<glyph unicode="&#xf27b;" d="M427 240l-117 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 142l60 -142zM213 109l81 -49l-22 91l71 62l-93 8l-37 86v-198z" />
+<glyph unicode="&#xf27c;" d="M427 251l-117 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141zM213 119l81 -48l-22 91l71 62l-93 8l-37 86l-36 -86l-93 -8l70 -62l-21 -91z" />
+<glyph unicode="&#xf27d;" d="M213 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141l154 -13l-117 -101l35 -150z" />
+<glyph unicode="&#xf27e;" horiz-adv-x="384" d="M85 192l-42 -43l-43 43l43 43zM314 284l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM213 324v-81l40 41zM253 100l-40 41v-81zM341 235l43 -43l-43 -43l-42 43z" />
+<glyph unicode="&#xf27f;" horiz-adv-x="341" d="M192 324v-69l-43 43v107h22l121 -121l-64 -65l-30 30l34 35zM30 363l311 -312l-30 -30l-49 49l-91 -91h-22v162l-98 -98l-30 30l120 119l-141 141zM192 60l40 40l-40 41v-81z" />
+<glyph unicode="&#xf280;" horiz-adv-x="384" d="M240 192l49 49q10 -24 10 -49q0 -26 -10 -50zM353 305q31 -51 31 -111q0 -61 -33 -113l-25 25q21 41 21 86q0 46 -21 86zM271 284l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM171 324v-81l40 41zM211 100l-40 41v-81z" />
+<glyph unicode="&#xf281;" horiz-adv-x="271" d="M128 -64v43h43v-43h-43zM43 -64v43h42v-43h-42zM213 -64v43h43v-43h-43zM271 326l-92 -91l92 -92l-122 -122h-21v162l-98 -98l-30 30l119 120l-119 119l30 30l98 -98v162h21zM171 366v-80l40 40zM211 143l-40 40v-80z" />
+<glyph unicode="&#xf282;" horiz-adv-x="271" d="M271 284l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM171 324v-81l40 41zM211 100l-40 41v-81z" />
+<glyph unicode="&#xf283;" d="M149 384h128l39 -43h68q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h67zM213 64q44 0 75.5 31.5t31.5 75.5t-31.5 75t-75.5 31t-75 -31t-31 -75t31 -75.5t75 -31.5zM213 85l-26 59 l-59 27l59 26l26 59l27 -59l59 -26l-59 -27z" />
+<glyph unicode="&#xf284;" d="M158 224l-1 -2l-78 135q58 48 134 48q23 0 47 -5zM417 256h-206l78 135q46 -17 79.5 -52.5t48.5 -82.5zM422 235q5 -22 5 -43q0 -83 -57 -144l-101 176l-6 11h159zM140 192l24 -43h-160q-4 22 -4 43q0 82 56 144zM10 128h206l-78 -135q-46 17 -79.5 52.5t-48.5 82.5z M250 128l20 34l78 -135q-59 -48 -135 -48q-22 0 -46 5z" />
+<glyph unicode="&#xf285;" d="M384 341q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h68l38 43h128l39 -43h68zM384 43v256h-171v-22q-44 0 -75 -31t-31 -75.5t31 -75.5t75 -31v-21h171zM320 170.5 q0 -44.5 -31 -75.5t-76 -31v38q29 0 49 20t20 48.5t-20 48.5t-49 20v38q45 0 76 -31t31 -75.5zM145 170.5q0 28.5 20 48.5t48 20v-137q-28 0 -48 20t-20 48.5z" />
+<glyph unicode="&#xf286;" horiz-adv-x="299" d="M107 21v43l64 -64l-64 -64v43h-107v42h107zM192 21h107v-42h-107v42zM149.5 277q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5zM256 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-213 q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h213zM43 405v-224q0 24 36.5 39t70 15t70 -15t36.5 -39v224h-213z" />
+<glyph unicode="&#xf287;" d="M384 341q18 0 30.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-149v45q45 7 75.5 43t30.5 83h-42q0 -36 -25 -61t-60.5 -25t-60.5 25t-25 61h-43q0 -47 30.5 -83t76.5 -43v-45h-149q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h67l39 43h128 l39 -43h68zM256 171v85q0 18 -12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5v-85q0 -18 12.5 -30.5t30 -12.5t30 12.5t12.5 30.5z" />
+<glyph unicode="&#xf288;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h67l39 42h128l39 -42h68zM213 299q-44 0 -75 -31.5t-31 -75.5q0 -10 2 -21h44q-4 10 -4 21q0 27 19 45.5t45 18.5h85 q-32 43 -85 43zM213 85q44 0 75.5 31.5t31.5 75.5q0 12 -2 21h-45q4 -10 4 -21q0 -27 -18.5 -45.5t-45.5 -18.5h-85q33 -43 85 -43z" />
+<glyph unicode="&#xf289;" horiz-adv-x="299" d="M107 21v43l64 -64l-64 -64v43h-107v42h107zM192 21h107v-42h-107v42zM256 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h213zM149.5 320q17.5 0 30 12.5t12.5 30t-12.5 30 t-30 12.5t-30 -12.5t-12.5 -30t12.5 -30t30 -12.5z" />
+<glyph unicode="&#xf28a;" d="M256 341h171v-320h-171q0 -17 -12.5 -29.5t-30.5 -12.5h-170q-18 0 -30.5 12.5t-12.5 29.5v320q0 18 12.5 30.5t30.5 12.5h21v21q0 9 6.5 15.5t14.5 6.5h86q8 0 14.5 -6.5t6.5 -15.5v-21h21q18 0 30.5 -12.5t12.5 -30.5zM213 64v43h-42v-43h42zM213 256v43h-42v-43h42z M299 64v43h-43v-43h43zM299 256v43h-43v-43h43zM384 64v43h-43v-43h43zM384 256v43h-43v-43h43z" />
+<glyph unicode="&#xf28b;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h67l39 42h128l39 -42h68zM277 117l75 75l-75 75v-54h-128v54l-74 -75l74 -75v54h128v-54z" />
+<glyph unicode="&#xf28c;" d="M145 192q0 68 68.5 68t68.5 -68t-68.5 -68t-68.5 68zM149 405h128l39 -42h68q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h67zM213 85q44 0 75.5 31.5t31.5 75.5t-31.5 75.5 t-75.5 31.5t-75 -31.5t-31 -75.5t31 -75.5t75 -31.5z" />
+<glyph unicode="&#xf28d;" horiz-adv-x="341" d="M299 405q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v256l128 128h171zM192 85v43h-43v-43h43zM192 171v106h-43v-106h43z" />
+<glyph unicode="&#xf28e;" horiz-adv-x="400" d="M354 341l1 -249l-242 242l50 50h149q17 0 29.5 -12.5t12.5 -30.5zM27 365l373 -372l-27 -28l-40 41q-10 -6 -21 -6h-213q-18 0 -30.5 12.5t-12.5 30.5v239l-56 56z" />
+<glyph unicode="&#xf28f;" horiz-adv-x="341" d="M299 405q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v256l128 128h171zM171 277v86h-43v-86h43zM235 277v86h-43v-86h43zM299 277v86h-43v-86h43z" />
+<glyph unicode="&#xf290;" horiz-adv-x="341" d="M341 363v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 29.5v256l128 128h171q17 0 29.5 -12.5t12.5 -29.5zM107 43v42h-43v-42h43zM277 43v42h-42v-42h42zM107 128v85h-43v-85h43zM192 43v85h-43v-85h43zM192 171v42h-43v-42h43zM277 128v85h-42v-85 h42z" />
+<glyph unicode="&#xf291;" horiz-adv-x="469" d="M427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-29.5 -12.5h-150l43 -64v-21h-171v21l43 64h-149q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h384zM427 149v214h-384v-214h384z" />
+<glyph unicode="&#xf292;" horiz-adv-x="469" d="M427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -18 -12.5 -30.5t-29.5 -12.5h-150v-43h43v-42h-171v42h43v43h-149q-18 0 -30.5 12.5t-12.5 30.5v256q0 17 12.5 29.5t30.5 12.5h384zM427 107v256h-384v-256h384z" />
+<glyph unicode="&#xf293;" horiz-adv-x="384" d="M299 107h85v-107h-107v65l-85 90l-85 -90v-65h-107v107h85l86 85v68q-19 7 -31 23.5t-12 36.5q0 27 18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5q0 -20 -12 -36.5t-31 -23.5v-68z" />
+<glyph unicode="&#xf294;" horiz-adv-x="512" d="M469 320h-281l-43 43h324v-43zM41 413l42 -42l372 -373l-27 -27l-50 50h-378v64h43v235q0 15 10 27l-39 39zM85 314v-229h229zM491 277q8 0 14.5 -6t6.5 -15v-213q0 -9 -6.5 -15.5t-14.5 -6.5h-4l-64 64h46v150h-85v-111l-43 43v89q0 9 6.5 15t15.5 6h128z" />
+<glyph unicode="&#xf295;" horiz-adv-x="512" d="M85 320v-235h214v-64h-299v64h43v235q0 18 12.5 30.5t29.5 12.5h384v-43h-384zM491 277q8 0 14.5 -6t6.5 -15v-213q0 -9 -6.5 -15.5t-14.5 -6.5h-128q-9 0 -15.5 6.5t-6.5 15.5v213q0 9 6.5 15t15.5 6h128zM469 85v150h-85v-150h85z" />
+<glyph unicode="&#xf296;" horiz-adv-x="256" d="M43 -43v43h170v-43h-170zM213 426q18 0 30.5 -12t12.5 -30v-299q0 -17 -12.5 -29.5t-30.5 -12.5h-170q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5zM213 128v213h-170v-213h170z" />
+<glyph unicode="&#xf297;" horiz-adv-x="384" d="M299 384l85 -85v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h256zM192 43q27 0 45.5 18.5t18.5 45t-18.5 45.5t-45.5 19t-45.5 -19t-18.5 -45.5t18.5 -45t45.5 -18.5zM256 256v85h-213v-85h213z" />
+<glyph unicode="&#xf298;" d="M277 288l-64 -64l-64 64v117h128v-117zM117 256l64 -64l-64 -64h-117v128h117zM149 96l64 64l64 -64v-117h-128v117zM309 256h118v-128h-118l-64 64z" />
+<glyph unicode="&#xf299;" horiz-adv-x="469" d="M234.5 277q35.5 0 60.5 -25t25 -60t-25 -60t-60.5 -25t-60.5 25t-25 60t25 60t60.5 25zM425 213h44v-42h-44q-7 -67 -54.5 -114.5t-114.5 -55.5v-44h-43v44q-66 8 -114 55.5t-55 114.5h-44v42h44q7 67 55 114.5t114 55.5v44h43v-44q67 -8 114.5 -55.5t54.5 -114.5z M235 43q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-106 -43.5t-44 -105.5t44 -105.5t106 -43.5z" />
+<glyph unicode="&#xf29a;" horiz-adv-x="469" d="M425 213h44v-42h-43q-4 -36 -21 -68l-32 32q11 28 11 57q0 62 -43.5 105.5t-105.5 43.5q-30 0 -57 -11l-32 32q32 17 67 21v44h43v-44q67 -8 114.5 -55.5t54.5 -114.5zM43 357l27 27l357 -357l-27 -27l-44 44q-44 -36 -100 -43v-44h-43v44q-66 8 -114 55.5t-55 114.5h-44 v42h44q6 56 42 100zM326 74l-210 209q-31 -40 -31 -91q0 -62 44 -105.5t106 -43.5q50 0 91 31z" />
+<glyph unicode="&#xf29b;" horiz-adv-x="469" d="M425 213h44v-42h-44q-7 -67 -54.5 -114.5t-114.5 -55.5v-44h-43v44q-66 8 -114 55.5t-55 114.5h-44v42h44q7 67 55 114.5t114 55.5v44h43v-44q67 -8 114.5 -55.5t54.5 -114.5zM235 43q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-106 -43.5t-44 -105.5 t44 -105.5t106 -43.5z" />
+<glyph unicode="&#xf29c;" horiz-adv-x="384" d="M192 427q80 0 136 -56.5t56 -135.5v-214q0 -26 -18.5 -45t-45.5 -19h-128v43h149v21h-85v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-27 0 -45.5 19t-18.5 45v150q0 79 56 135.5t136 56.5z" />
+<glyph unicode="&#xf29d;" horiz-adv-x="384" d="M192 427q80 0 136 -56.5t56 -135.5v-150q0 -26 -18.5 -45t-45.5 -19h-64v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-27 0 -45.5 19t-18.5 45v150q0 79 56 135.5t136 56.5z" />
+<glyph unicode="&#xf29e;" horiz-adv-x="469" d="M235 341q62 0 105.5 -43.5t43.5 -105.5h-43q0 44 -31 75.5t-75 31.5t-75.5 -31.5t-31.5 -75.5h-43q0 62 44 105.5t106 43.5zM256 143v-70l73 -73l-30 -30l-64 64l-64 -64l-30 30l72 73v70q-14 6 -23 19.5t-9 29.5q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5 q0 -35 -32 -49zM235 427q97 0 165.5 -69t68.5 -166h-42q0 80 -56.5 136t-136 56t-135.5 -56t-56 -136h-43q0 97 69 166t166 69z" />
+<glyph unicode="&#xf29f;" horiz-adv-x="469" d="M85 405v-85h43v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5zM171 107v42h128v-42q0 -21 -12 -37.5t-31 -22.5v-90h-43v90q-19 6 -30.5 22.5t-11.5 37.5zM0 107v42h128v-42q0 -21 -12 -37.5t-31 -22.5v-90h-42v90q-19 6 -31 22.5t-12 37.5zM427 320h42v-128 h-128v128h43v85q0 9 6.5 15.5t15 6.5t15 -6.5t6.5 -15.5v-85zM256 405v-85h43v-128h-128v128h42v85q0 9 6.5 15.5t15 6.5t15 -6.5t6.5 -15.5zM341 107v42h128v-42q0 -21 -11.5 -37.5t-30.5 -22.5v-90h-43v90q-19 6 -31 22.5t-12 37.5z" />
+<glyph unicode="&#xf2a0;" horiz-adv-x="299" d="M277 299h22v-128l-64 -128v-64h-171v64l-64 128v128h21v64q0 17 12.5 29.5t30.5 12.5h171q17 0 29.5 -12.5t12.5 -29.5v-64zM64 363v-64h43v42h21v-42h43v42h21v-42h43v64h-171z" />
+<glyph unicode="&#xf2a1;" horiz-adv-x="256" d="M214 299q15 0 28.5 -13.5t13.5 -29.5v-117l-75 -75v-64h-106v64l-75 75v117q0 16 13.5 29.5t28.5 13.5h1v85h42v-85h86v85h42z" />
+<glyph unicode="&#xf2a2;" horiz-adv-x="469" d="M149 202.5q0 -13.5 -9 -22.5t-22.5 -9t-23 9t-9.5 22.5t9.5 23t23 9.5t22.5 -9.5t9 -23zM299 309.5q0 -13.5 -9.5 -23t-22.5 -9.5h-64q-14 0 -23 9.5t-9 23t9 22.5t23 9h64q13 0 22.5 -9t9.5 -22.5zM160 128q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5 t-9.5 22.5t9.5 22.5t22.5 9.5zM235 427q97 0 165.5 -69t68.5 -166t-68.5 -166t-165.5 -69t-166 69t-69 166t69 166t166 69zM234.5 0q79.5 0 136 56.5t56.5 135.5t-56.5 135.5t-136 56.5t-135.5 -56.5t-56 -135.5t56 -135.5t135.5 -56.5zM352 235q13 0 22.5 -9.5t9.5 -23 t-9.5 -22.5t-22.5 -9t-22.5 9t-9.5 22.5t9.5 23t22.5 9.5zM309.5 128q13.5 0 22.5 -9.5t9 -22.5t-9 -22.5t-22.5 -9.5t-23 9.5t-9.5 22.5t9.5 22.5t23 9.5z" />
+<glyph unicode="&#xf2a3;" d="M384 384q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v213q0 18 12.5 30.5t30.5 12.5h341zM192 320v-43h43v43h-43zM192 256v-43h43v43h-43zM128 320v-43h43v43h-43zM128 256v-43h43v43h-43zM107 213v43h-43v-43h43 zM107 277v43h-43v-43h43zM299 128v43h-171v-43h171zM299 213v43h-43v-43h43zM299 277v43h-43v-43h43zM363 213v43h-43v-43h43zM363 277v43h-43v-43h43zM213 -43l-85 86h171z" />
+<glyph unicode="&#xf2a4;" d="M384 341q18 0 30.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h341zM192 277v-42h43v42h-43zM192 213v-42h43v42h-43zM128 277v-42h43v42h-43zM128 213v-42h43v42h-43zM107 171v42h-43v-42h43 zM107 235v42h-43v-42h43zM299 85v43h-171v-43h171zM299 171v42h-43v-42h43zM299 235v42h-43v-42h43zM363 171v42h-43v-42h43zM363 235v42h-43v-42h43z" />
+<glyph unicode="&#xf2a5;" horiz-adv-x="512" d="M469 64h43v-43h-512v43h43v320h426v-320zM299 64v21h-86v-21h86zM427 128v213h-342v-213h342z" />
+<glyph unicode="&#xf2a6;" horiz-adv-x="512" d="M427 64h85q0 -18 -12.5 -30.5t-30.5 -12.5h-426q-18 0 -30.5 12.5t-12.5 30.5h85q-17 0 -29.5 12.5t-12.5 30.5v234q0 18 12.5 30.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-234q0 -18 -12.5 -30.5t-29.5 -12.5zM85 341v-234h342v234h-342zM256 43q9 0 15 6t6 15 t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6z" />
+<glyph unicode="&#xf2a7;" horiz-adv-x="512" d="M427 64h85v-43h-512v43h85q-17 0 -29.5 12.5t-12.5 30.5v213q0 18 12.5 30.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-29.5 -12.5zM85 320v-213h342v213h-342z" />
+<glyph unicode="&#xf2a8;" horiz-adv-x="384" d="M341 213q0 -36 -19 -70l-26 27q9 21 9 43h36zM256 210l-128 127v4q0 27 18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5v-128v-1.5v-1.5zM27 384l357 -357l-27 -27l-89 89q-26 -15 -55 -19v-70h-42v70q-54 8 -91 49t-37 94h36q0 -46 33.5 -77t79.5 -31q25 0 49 11l-35 35 q-7 -2 -14 -2q-27 0 -45.5 19t-18.5 45v16l-128 128z" />
+<glyph unicode="&#xf2a9;" horiz-adv-x="299" d="M149.5 139q-26.5 0 -45.5 18.5t-19 45.5v128q0 26 19 45t45.5 19t45 -19t18.5 -45v-128q0 -27 -18.5 -45.5t-45 -18.5zM124 333v-132q0 -11 7.5 -18.5t18 -7.5t18 7.5t7.5 18.5v132q0 10 -7.5 17.5t-18 7.5t-18 -7.5t-7.5 -17.5zM262 203h37q0 -54 -37.5 -95t-90.5 -49 v-70h-43v70q-53 8 -90.5 49t-37.5 95h36q0 -47 34 -78t79.5 -31t79 31t33.5 78z" />
+<glyph unicode="&#xf2aa;" horiz-adv-x="299" d="M43 -64v43h42v-43h-42zM149.5 171q-26.5 0 -45.5 18.5t-19 45.5v128q0 26 19 45t45.5 19t45 -19t18.5 -45v-128q0 -27 -18.5 -45.5t-45 -18.5zM128 -64v43h43v-43h-43zM213 -64v43h43v-43h-43zM299 235q0 -54 -37.5 -95t-90.5 -49v-70h-43v70q-53 8 -90.5 49t-37.5 95h36 q0 -47 34 -78t79.5 -31t79 31t33.5 78h37z" />
+<glyph unicode="&#xf2ab;" horiz-adv-x="299" d="M149.5 149q-26.5 0 -45.5 19t-19 45v128q0 27 19 45.5t45.5 18.5t45 -18.5t18.5 -45.5v-128q0 -26 -18.5 -45t-45 -19zM262 213h37q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-43v70q-53 8 -90.5 49t-37.5 94h36q0 -46 34 -77t79.5 -31t79 31t33.5 77z" />
+<glyph unicode="&#xf2ac;" horiz-adv-x="341" d="M192 425q64 -8 106.5 -56t42.5 -113h-149v169zM0 128v85h341v-85q0 -71 -50 -121t-120.5 -50t-120.5 50t-50 121zM149 425v-169h-149q0 65 43 113t106 56z" />
+<glyph unicode="&#xf2ad;" d="M384 64v171h43v-171h-43zM384 -21v42h43v-42h-43zM0 -21l427 426v-128h-86v-298h-341z" />
+<glyph unicode="&#xf2ae;" horiz-adv-x="469" d="M395 235q-40 0 -68 -28.5t-28 -67.5v-6q-22 -19 -22 -48v-64h-277l405 406v-193q-9 1 -10 1zM448 107q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-8 0 -14.5 6t-6.5 15v85q0 9 6.5 15.5t14.5 6.5v32q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5v-32zM427 107v32 q0 13 -9.5 22.5t-23 9.5t-22.5 -9.5t-9 -22.5v-32h64z" />
+<glyph unicode="&#xf2af;" horiz-adv-x="448" d="M427 427v-367l-184 183zM80 352l368 -368l-27 -27l-43 43h-378l189 189l-136 136z" />
+<glyph unicode="&#xf2b0;" d="M384 302l-281 -281h281v281zM427 405v-426h-427z" />
+<glyph unicode="&#xf2b1;" horiz-adv-x="509" d="M405 203q-66 0 -113 -47t-47 -113q0 -9 2 -22h-247l427 427l-1 -247q-12 2 -21 2zM484 32l23 -17q3 -3 1 -7l-21 -37q-2 -4 -7 -3l-26 11q-8 -6 -18 -10l-4 -29q-1 -4 -6 -4h-42q-5 0 -6 4l-4 29q-9 3 -18 10l-26 -11q-5 -1 -7 3l-21 37q-2 4 1 7l23 17q-1 5 -1 10.5 t1 10.5l-23 18q-3 3 -1 7l21 37q3 3 7 2l26 -11q8 6 18 11l4 28q1 4 6 4h42q5 0 6 -4l4 -28q9 -4 18 -11l26 11q5 1 7 -2l21 -37q2 -4 -1 -7l-23 -18q1 -4 1 -10q0 -4 -1 -11zM405 11q13 0 22.5 9t9.5 22.5t-9.5 23t-22.5 9.5t-22.5 -9.5t-9.5 -23t9.5 -22.5t22.5 -9z" />
+<glyph unicode="&#xf2b2;" d="M0 -21l427 426v-426h-427z" />
+<glyph unicode="&#xf2b3;" horiz-adv-x="384" d="M250 245l-15 15l59 60l-59 60l15 15l49 -49v81h10l61 -61l-46 -46l46 -46l-61 -61h-10v81zM320 386v-40l20 20zM320 294v-40l20 20zM363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75 q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+<glyph unicode="&#xf2b4;" horiz-adv-x="512" d="M256 256q-51 0 -98 -15v-66q0 -14 -12 -20q-31 -15 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-25 23 -57 39q-12 6 -12 19v66q-47 16 -98 16z" />
+<glyph unicode="&#xf2b5;" d="M320 213v64h-85v86h85v64l107 -107zM363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5 q36 -12 76 -12z" />
+<glyph unicode="&#xf2b6;" horiz-adv-x="384" d="M363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12zM341 192q0 62 -43.5 105.5 t-105.5 43.5v43q80 0 136 -56t56 -136h-43zM256 192q0 27 -18.5 45.5t-45.5 18.5v43q44 0 75.5 -31.5t31.5 -75.5h-43z" />
+<glyph unicode="&#xf2b7;" horiz-adv-x="384" d="M363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12zM363 363q8 0 14.5 -6.5 t6.5 -15.5v-85q0 -9 -6.5 -15t-14.5 -6h-107q-9 0 -15 6t-6 15v85q0 9 6 15.5t15 6.5v10q0 22 15.5 38t37.5 16t38 -16t16 -38v-10zM346 363v10q0 15 -11 26t-26 11t-25.5 -11t-10.5 -26v-10h73z" />
+<glyph unicode="&#xf2b8;" horiz-adv-x="512" d="M139 331v-75h-32v128h128v-32h-75l96 -96l128 128l21 -21l-149 -150zM506 92q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-27 24 -57 40q-12 5 -12 19v66q-47 15 -98 15t-98 -15v-66q0 -14 -12 -20q-32 -16 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15 q105 100 250 100t250 -100z" />
+<glyph unicode="&#xf2b9;" horiz-adv-x="384" d="M363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12zM192 384h192v-149h-128 l-64 -64v213z" />
+<glyph unicode="&#xf2ba;" horiz-adv-x="384" d="M299 384v-149h-43v149h43zM363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12z M341 384h43v-149h-43v149z" />
+<glyph unicode="&#xf2bb;" horiz-adv-x="512" d="M506 92q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-26 24 -57 40q-12 5 -12 19v66q-47 15 -98 15t-98 -15v-66q0 -14 -12 -20q-32 -16 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100zM451 314l-76 -75l-30 30l76 76zM277 405v-106 h-42v106h42zM137 239q-74 75 -76 75l30 31l76 -76z" />
+<glyph unicode="&#xf2bc;" horiz-adv-x="384" d="M213 256v-43h-42v43h42zM299 256v-43h-43v43h43zM363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -93 141 -141l47 47 q9 9 22 5q36 -12 76 -12zM341 256h43v-43h-43v43z" />
+<glyph unicode="&#xf2bd;" horiz-adv-x="384" d="M299 384v-107h-22v107h22zM256 341v-64h-64v22h43v21h-43v64h64v-21h-43v-22h43zM320 384h64v-64h-43v-43h-21v107zM363 341v22h-22v-22h22zM363 117q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5 h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22l-47 -47q48 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+<glyph unicode="&#xf2be;" horiz-adv-x="384" d="M77 218q47 -93 141 -141l47 47q9 10 22 5q36 -12 76 -12q8 0 14.5 -6t6.5 -15v-75q0 -8 -6.5 -14.5t-14.5 -6.5q-99 0 -182.5 48.5t-132 132t-48.5 182.5q0 8 6.5 14.5t14.5 6.5h75q9 0 15 -6.5t6 -14.5q0 -40 12 -76q4 -13 -5 -22z" />
+<glyph unicode="&#xf2bf;" d="M364 343q63 -63 63 -151t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t150.5 62.5h22v-176q21 -12 21 -37q0 -18 -12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5q0 24 21 37v45q-28 -7 -46 -30t-18 -52q0 -35 25 -60t60.5 -25t60.5 25t25 60t-25 60l30 30 q37 -37 37 -90t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 47 30.5 82.5t76.5 43.5v43q-64 -8 -106.5 -56t-42.5 -113q0 -71 50 -121t120.5 -50t120.5 50t50 121q0 70 -50 121z" />
+<glyph unicode="&#xf2c0;" d="M332 144l-34 35q1 7 1 13q0 35 -25 60t-61 25q-4 0 -13 -1l-34 35q23 9 47 9q53 0 90.5 -37.5t37.5 -90.5q0 -25 -9 -48zM213 363q-42 0 -80 -20l-31 31q52 31 111 31q89 0 151.5 -62.5t62.5 -150.5q0 -60 -32 -111l-31 31q20 38 20 80q0 71 -50 121t-121 50zM27 395 l21 -22l357 -357l-27 -27l-160 161l-5 -1q-17 0 -29.5 12.5t-12.5 30.5v4l-34 34q-9 -19 -9 -38q0 -49 43 -74l-22 -37q-29 17 -46.5 46.5t-17.5 64.5q0 38 21 69l-31 31q-32 -44 -32 -100q0 -47 23 -86t62 -62l-22 -37q-48 29 -77 78t-29 107q0 73 45 131l-45 45z" />
+<glyph unicode="&#xf2c1;" d="M213.5 213q17.5 0 30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5zM341 171q0 -35 -17 -64.5t-47 -46.5l-21 37q43 25 43 74q0 35 -25 60t-60.5 25t-60.5 -25t-25 -60q0 -49 43 -74l-22 -37q-29 17 -46.5 46.5t-17.5 64.5q0 53 37.5 90.5 t90.5 37.5t90.5 -37.5t37.5 -90.5zM213.5 384q88.5 0 151 -62.5t62.5 -150.5q0 -59 -29 -108t-78 -77l-21 37q39 23 62 62t23 86q0 70 -50 120t-120.5 50t-120.5 -50t-50 -120q0 -47 23 -86t62 -62l-22 -37q-48 28 -77 77t-29 108q0 88 62.5 150.5t151 62.5z" />
+<glyph unicode="&#xf2c2;" d="M26 317l270 110l15 -36l-177 -71h250q18 0 30.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v256q0 14 7.5 24.5t18.5 15.5zM106.5 21q26.5 0 45.5 19t19 45.5t-19 45t-45.5 18.5t-45 -18.5t-18.5 -45t18.5 -45.5t45 -19z M384 192v85h-341v-85h256v43h42v-43h43z" />
+<glyph unicode="&#xf2c3;" horiz-adv-x="469" d="M256 192h149v-32h-149v32zM256 213h149h-149zM256 107h149h-149zM427 363q17 0 29.5 -12.5t12.5 -30.5v-277q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v277q0 18 12.5 30.5t30.5 12.5h384zM427 43v277h-192v-277h192z" />
+<glyph unicode="&#xf2c4;" horiz-adv-x="331" d="M59 297q44 44 106.5 44t106.5 -44l-31 -30q-31 31 -75.5 31t-76.5 -31zM165.5 427q96.5 0 165.5 -69l-30 -30q-56 56 -135.5 56t-135.5 -56l-30 30q69 69 165.5 69zM226 234q10 0 17.5 -7t6.5 -17v-207q0 -10 -7 -17t-17 -7h-122q-10 0 -17 7t-7 17v207q0 10 7 17.5 t17 7.5zM229 21v171h-128v-171h128z" />
+<glyph unicode="&#xf2c5;" horiz-adv-x="332" d="M230 256q9 0 15 -6.5t6 -14.5v-256q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15 6.5t-6 15.5v256q0 8 6 14.5t15 6.5h128zM166 128q18 0 30.5 12.5t12.5 30t-12.5 30t-30.5 12.5t-30.5 -12.5t-12.5 -30t12.5 -30t30.5 -12.5zM60 319q44 44 106 44t106 -44l-31 -30 q-31 31 -75 31t-76 -31zM166 448q98 0 166 -69l-30 -30q-56 56 -136 56q-79 0 -136 -56l-30 30q69 69 166 69z" />
+<glyph unicode="&#xf2c6;" horiz-adv-x="384" d="M367 322q-40 36 -90 36t-89 -36l-17 17q44 45 106 45t107 -45zM348 305l-17 -17q-22 21 -54 21t-53 -21l-17 17q30 30 70.5 30t70.5 -30zM341 171q18 0 30.5 -12.5t12.5 -30.5v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v85q0 18 12.5 30.5 t30.5 12.5h213v85h43v-85h42zM107 64v43h-43v-43h43zM181 64v43h-42v-43h42zM256 64v43h-43v-43h43z" />
+<glyph unicode="&#xf2c7;" horiz-adv-x="384" d="M358 220q11 -3 18.5 -14.5t7.5 -24.5v-117q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v85q0 18 12.5 30.5t30.5 12.5h268l-300 109l15 40zM85 85v43h-42v-43h42zM341 85v43h-213v-43h213z" />
+<glyph unicode="&#xf2c8;" horiz-adv-x="299" d="M235 427q26 0 45 -19t19 -45v-342q0 -26 -19 -45t-45 -19h-171q-27 0 -45.5 19t-18.5 45v342q0 26 18.5 45t45.5 19h171zM192 0v21h-85v-21h85zM261 64v299h-224v-299h224z" />
+<glyph unicode="&#xf2c9;" horiz-adv-x="299" d="M256 426q18 0 30.5 -12t12.5 -30v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v384q0 18 12.5 30.5t30.5 12.5zM256 43v298h-213v-298h213zM235 171l-86 -86l-85 86h64v106h43v-106h64z" />
+<glyph unicode="&#xf2ca;" horiz-adv-x="384" d="M213 273l-85 -85l85 -86l-21 -21l-85 85l-86 -85l-21 21l85 86l-85 85l21 21l86 -85l85 85zM341 427q18 0 30.5 -12.5t12.5 -30.5v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v64h43v-43h213v342h-213v-43h-43v64q0 18 12.5 30.5t30.5 12.5 h213z" />
+<glyph unicode="&#xf2cb;" horiz-adv-x="299" d="M171 299v-43h-43v43h43zM171 213v-128h-43v128h43zM256 426q18 0 30.5 -12t12.5 -30v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v384q0 18 12.5 30.5t30.5 12.5zM256 43v298h-213v-298h213z" />
+<glyph unicode="&#xf2cc;" horiz-adv-x="277" d="M224 427q22 0 37.5 -16t15.5 -38v-362q0 -22 -15.5 -38t-37.5 -16h-171q-22 0 -37.5 16t-15.5 38v362q0 22 15.5 38t37.5 16h171zM138.5 -21q13.5 0 23 9t9.5 22.5t-9.5 23t-23 9.5t-22.5 -9.5t-9 -23t9 -22.5t22.5 -9zM235 64v299h-192v-299h192z" />
+<glyph unicode="&#xf2cd;" horiz-adv-x="469" d="M427 341q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h384zM384 85v214h-299v-214h299zM192 107q-9 0 -15 6t-6 15v64q0 9 6 15t15 6v22q0 17 12.5 29.5t30 12.5t30 -12.5t12.5 -29.5 v-22q9 0 15.5 -6t6.5 -15v-64q0 -9 -6.5 -15t-15.5 -6h-85zM209 235v-22h51v22q0 10 -7.5 17.5t-18 7.5t-18 -7.5t-7.5 -17.5z" />
+<glyph unicode="&#xf2ce;" horiz-adv-x="469" d="M0 299q0 17 12.5 29.5t30.5 12.5h384q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 29.5v214zM384 299h-299v-214h299v214z" />
+<glyph unicode="&#xf2cf;" horiz-adv-x="363" d="M320 427q18 0 30.5 -12.5t12.5 -30.5v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v64h43v-43h213v342h-213v-43h-43v64q0 18 12.5 30.5t30.5 12.5h213zM145 213q10 0 18 -8t8 -19v-75q0 -10 -8.5 -18t-19.5 -8h-117q-10 0 -18 8.5t-8 19.5v75 q0 9 8 17t18 8v32q0 22 18 38t41 16t41.5 -16t18.5 -38v-32zM117 213v32q0 13 -9 20.5t-22.5 7.5t-23 -7.5t-9.5 -20.5v-32h64z" />
+<glyph unicode="&#xf2d0;" horiz-adv-x="299" d="M107 107q-9 0 -15.5 6t-6.5 15v64q0 9 6.5 15t15.5 6v22q0 17 12.5 29.5t30 12.5t30 -12.5t12.5 -29.5v-22q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-85zM124 235v-22h51v22q0 10 -7.5 17.5t-18 7.5t-18 -7.5t-7.5 -17.5zM256 427q18 0 30.5 -12.5t12.5 -30.5v-384 q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v384q0 18 12.5 30.5t30.5 12.5h213zM256 43v298h-213v-298h213z" />
+<glyph unicode="&#xf2d1;" horiz-adv-x="426" d="M386 284q40 -39 40 -92t-40 -90l-21 22q29 30 29 70t-29 68zM341 239q20 -21 20 -47t-20 -45l-21 22q18 24 0 49zM256 427q18 0 30.5 -12.5t12.5 -30.5v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v384q0 18 12.5 30.5t30.5 12.5h213zM256 21 v342h-213v-342h213z" />
+<glyph unicode="&#xf2d2;" horiz-adv-x="256" d="M21 -64v43h43v-43h-43zM107 -64v43h42v-43h-42zM192 -64v43h43v-43h-43zM213 448q18 0 30.5 -12.5t12.5 -30.5v-341q0 -18 -12.5 -30.5t-30.5 -12.5h-170q-18 0 -30.5 12.5t-12.5 30.5v341q0 18 12.5 30.5t30.5 12.5h170zM213 107v256h-170v-256h170z" />
+<glyph unicode="&#xf2d3;" horiz-adv-x="385" d="M189 181l23 -19q4 -4 2 -6l-21 -37q-2 -2 -7 -2l-27 11q-13 -9 -19 -11l-5 -27q-4 -5 -6 -5h-43q-2 0 -3.5 1.5t-0.5 3.5l-4 27q-7 2 -20 11l-29 -9q-3 -2 -7 3l-21 36q0 4 2 8l23 17v22l-23 17q-4 4 -2 6l21 37q2 2 7 2l27 -11q13 9 20 11l4 27q4 5 6 5h43q6 0 6 -5 l5 -27q6 -2 19 -11l27 9q3 2 7 -3l21 -36q0 -4 -2 -6l-23 -17v-22zM107.5 149q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5zM342 427q18 0 30.5 -12.5t12.5 -30.5v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5 t-12.5 30.5v64h43v-43h213v342h-213v-43h-43v64q0 18 12.5 30.5t30.5 12.5h213z" />
+<glyph unicode="&#xf2d4;" horiz-adv-x="299" d="M256 426q18 0 30.5 -12t12.5 -30v-384q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v384q0 18 12.5 30.5t30.5 12.5zM256 43v298h-213v-298h213z" />
+<glyph unicode="&#xf2d5;" horiz-adv-x="299" d="M256 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h213zM149.5 363q-17.5 0 -30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5t30 12.5t12.5 30.5t-12.5 30.5t-30 12.5zM149 21 q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75 -31.5t-31 -75.5t31 -75.5t75 -31.5zM149.5 192q26.5 0 45 -18.5t18.5 -45.5t-18.5 -45.5t-45 -18.5t-45.5 18.5t-19 45.5t19 45.5t45.5 18.5z" />
+<glyph unicode="&#xf2d6;" horiz-adv-x="384" d="M320 448q27 0 45.5 -18.5t18.5 -45.5v-384q0 -27 -18.5 -45.5t-45.5 -18.5h-256q-27 0 -45.5 18.5t-18.5 45.5v384q0 27 18.5 45.5t45.5 18.5h256zM235 -21v21h-86v-21h86zM347 43v341h-310v-341h310z" />
+<glyph unicode="&#xf2d7;" horiz-adv-x="405" d="M352 448q22 0 37.5 -15.5t15.5 -37.5v-406q0 -22 -15.5 -37.5t-37.5 -15.5h-299q-22 0 -37.5 15.5t-15.5 37.5v406q0 22 15.5 37.5t37.5 15.5h299zM202.5 -43q13.5 0 23 9.5t9.5 23t-9.5 22.5t-23 9t-22.5 -9t-9 -22.5t9 -23t22.5 -9.5zM363 43v341h-320v-341h320z" />
+<glyph unicode="&#xf2d8;" horiz-adv-x="469" d="M427 363q17 0 29.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h384zM384 64v256h-299v-256h299z" />
+<glyph unicode="&#xf2d9;" horiz-adv-x="469" d="M427 320q17 0 29.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h162l-71 70l15 15l86 -85l85 85l15 -15l-70 -70h162zM427 21v256h-384v-256h384zM171 235l149 -86l-149 -85v171z" />
+<glyph unicode="&#xf2da;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-171v43h-106q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384zM427 85v256h-384v-256h384zM384 277v-42h-235v42h235zM384 192v-43h-235v43h235zM128 277v-42h-43v42 h43zM128 192v-43h-43v43h43z" />
+<glyph unicode="&#xf2db;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-171v43h-106q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384zM427 85v256h-384v-256h384zM320 213l-149 -85v171z" />
+<glyph unicode="&#xf2dc;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-171v43h-106q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384zM427 85v256h-384v-256h384z" />
+<glyph unicode="&#xf2dd;" horiz-adv-x="303" d="M218 299h85v-86h-21v-42q0 -18 -12.5 -30.5t-30.5 -12.5h-64v-65q26 -13 26 -42q0 -19 -14 -33t-33.5 -14t-33 14t-13.5 33q0 29 25 42v65h-64q-17 0 -29.5 12.5t-12.5 30.5v44q-26 13 -26 41q0 19 14 33t33 14t33 -14t14 -33q0 -28 -26 -41v-44h64v170h-42l64 86l64 -86 h-43v-170h64v42h-21v86z" />
+<glyph unicode="&#xf2de;" horiz-adv-x="405" d="M405 309v-228l-238 239h132q8 0 14.5 -6.5t6.5 -14.5v-75zM27 405l378 -378l-27 -27l-68 68q-6 -4 -11 -4h-256q-9 0 -15.5 6.5t-6.5 14.5v214q0 8 6.5 14.5t15.5 6.5h15l-58 58z" />
+<glyph unicode="&#xf2df;" d="M341 245l86 86v-278l-86 86v-75q0 -9 -6 -15t-15 -6h-299q-8 0 -14.5 6t-6.5 15v256q0 9 6.5 15t14.5 6h299q9 0 15 -6t6 -15v-75zM235 117l74 75l-74 75v-54h-128v54l-75 -75l75 -75v54h128v-54z" />
+<glyph unicode="&#xf2e0;" horiz-adv-x="384" d="M299 224l85 85v-234l-85 85v-75q0 -8 -6.5 -14.5t-15.5 -6.5h-256q-8 0 -14.5 6.5t-6.5 14.5v214q0 8 6.5 14.5t14.5 6.5h256q9 0 15.5 -6.5t6.5 -14.5v-75z" />
+<glyph unicode="&#xf2e1;" horiz-adv-x="341" d="M341 192q0 -40 -17 -75t-48 -59l-20 -122h-171l-20 122q-65 51 -65 134t65 134l20 122h171l20 -122q31 -24 48 -59t17 -75zM43 192q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf2e2;" horiz-adv-x="497" d="M249 350q-107 1 -190 -55l43 -53q49 29 107 38q108 15 187 -38l42 53q-83 56 -189 55zM248.5 389q138.5 0 248.5 -85l-248 -309l-249 309q110 85 248.5 85z" />
+<glyph unicode="&#xf2e3;" horiz-adv-x="469" d="M0 256q64 64 149.5 86.5t171 0t148.5 -86.5l-42 -43q-80 80 -192.5 80t-191.5 -80zM171 85q26 27 63.5 27t64.5 -27l-64 -64zM85 171q62 61 149.5 61t149.5 -61l-43 -43q-44 44 -106.5 44t-106.5 -44z" />
+<glyph unicode="&#xf2e4;" horiz-adv-x="512" d="M256 384q136 0 256 -91l-256 -314l-256 315q119 90 256 90zM277 107v128h-42v-128h42zM235 277h42v43h-42v-43z" />
+<glyph unicode="&#xf2e5;" horiz-adv-x="503" d="M482 107q8 0 14.5 -7t6.5 -15v-85q0 -8 -6.5 -14.5t-14.5 -6.5h-107q-8 0 -14.5 6.5t-6.5 14.5v85q0 8 6.5 15t14.5 7v32q0 22 15.5 37.5t38 15.5t38 -15.5t15.5 -37.5v-32zM461 107v32q0 12 -9.5 22t-22.5 10t-22.5 -10t-9.5 -22v-32h64zM322 139v-56l-75 -94l-247 310 q114 85 247.5 85t247.5 -85l-45 -56q-6 2 -21 2q-45 0 -76 -31t-31 -75z" />
+<glyph unicode="&#xf2e6;" horiz-adv-x="497" d="M497 299l-117 -145l-220 220q44 10 88 10q136 0 249 -85zM356 123l74 -74l-27 -27l-71 71l-83 -103l-1 -1v1l-248 309q35 27 79 47l-44 44l27 27z" />
+<glyph unicode="&#xf2e7;" horiz-adv-x="497" d="M249 350q-105 0 -190 -55l190 -237l189 237q-84 55 -189 55zM248.5 389q49.5 0 96 -11t80.5 -29.5t47 -27t25 -17.5l-248 -309v0l-249 309q12 9 25 17.5t47.5 27t80.5 29.5t95.5 11z" />
+<glyph unicode="&#xf2e8;" horiz-adv-x="497" d="M249 -10l-1 -1v1l-248 309q113 85 248.5 85t248.5 -85zM68 214q82 63 180.5 63t180.5 -63l-180 -224l-1 -1v1z" />
+<glyph unicode="&#xf2e9;" horiz-adv-x="320" d="M320 333l-247 -248h140v-42h-213v213h43v-141l247 248z" />
+<glyph unicode="&#xf2ea;" horiz-adv-x="341" d="M341 213v-42h-259l119 -120l-30 -30l-171 171l171 171l30 -30l-119 -120h259z" />
+<glyph unicode="&#xf2eb;" horiz-adv-x="273" d="M243 13l-72 72l30 30l72 -72zM41 277l96 96l96 -96h-75v-136l-128 -128l-30 30l115 115v119h-74z" />
+<glyph unicode="&#xf2ec;" horiz-adv-x="384" d="M354 299l30 -30l-192 -192l-149 149v-98h-43v171h171v-43h-98l119 -119z" />
+<glyph unicode="&#xf2ed;" horiz-adv-x="320" d="M107 341h213v-213h-43v141l-247 -248l-30 30l247 248h-140v42z" />
+<glyph unicode="&#xf2ee;" horiz-adv-x="341" d="M171 363l170 -171l-170 -171l-30 30l119 120h-260v42h260l-119 120z" />
+<glyph unicode="&#xf2ef;" horiz-adv-x="341" d="M213 363h128v-128l-49 49l-61 -62l-30 30l61 62zM128 363l-49 -49l113 -113v-180h-43v162l-100 101l-49 -49v128h128z" />
+<glyph unicode="&#xf2f0;" horiz-adv-x="469" d="M192 256v64h-64l107 107l106 -107h-64v-64h-85zM171 235v-86h-64v-64l-107 107l107 107v-64h64zM469 192l-106 -107v64h-64v86h64v64zM277 128v-64h64l-106 -107l-107 107h64v64h85z" />
+<glyph unicode="&#xf2f1;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM107 235l106 -107l107 107h-213z" />
+<glyph unicode="&#xf2f2;" horiz-adv-x="213" d="M0 245h213l-106 -106z" />
+<glyph unicode="&#xf2f3;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM256 299l-107 -107l107 -107v214z" />
+<glyph unicode="&#xf2f4;" horiz-adv-x="107" d="M107 299v-214l-107 107z" />
+<glyph unicode="&#xf2f5;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM171 299v-214l106 107z" />
+<glyph unicode="&#xf2f6;" horiz-adv-x="107" d="M0 85v214l107 -107z" />
+<glyph unicode="&#xf2f7;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213 260l-106 -106h213z" />
+<glyph unicode="&#xf2f8;" horiz-adv-x="213" d="M213 139h-213l107 106z" />
+<glyph unicode="&#xf2f9;" horiz-adv-x="256" d="M226 265l30 -30l-128 -128l-128 128l30 30l98 -98z" />
+<glyph unicode="&#xf2fa;" horiz-adv-x="158" d="M158 290l-98 -98l98 -98l-30 -30l-128 128l128 128z" />
+<glyph unicode="&#xf2fb;" horiz-adv-x="158" d="M30 320l128 -128l-128 -128l-30 30l98 98l-98 98z" />
+<glyph unicode="&#xf2fc;" horiz-adv-x="256" d="M128 277l128 -128l-30 -30l-98 98l-98 -98l-30 30z" />
+<glyph unicode="&#xf2fd;" horiz-adv-x="341" d="M171 277v86l170 -171l-170 -171v86h-171v170h171z" />
+<glyph unicode="&#xf2fe;" horiz-adv-x="256" d="M149 384v-302l77 76l30 -30l-128 -128l-128 128l30 30l77 -76v302h42z" />
+<glyph unicode="&#xf2ff;" horiz-adv-x="384" d="M384 213v-42h-302l76 -77l-30 -30l-128 128l128 128l30 -30l-76 -77h302z" />
+<glyph unicode="&#xf300;" horiz-adv-x="405" d="M363 299h42v-128h-323l76 -77l-30 -30l-128 128l128 128l30 -30l-76 -77h281v86z" />
+<glyph unicode="&#xf301;" horiz-adv-x="384" d="M0 213h302l-76 77l30 30l128 -128l-128 -128l-30 30l76 77h-302v42z" />
+<glyph unicode="&#xf302;" horiz-adv-x="448" d="M226 290l30 30l128 -128l-128 -128l-30 30l76 77h-302v42h302zM405 320h43v-256h-43v256z" />
+<glyph unicode="&#xf303;" horiz-adv-x="256" d="M107 0v302l-77 -76l-30 30l128 128l128 -128l-30 -30l-77 76v-302h-42z" />
+<glyph unicode="&#xf304;" horiz-adv-x="451" d="M138 298l139 -138l-139 -139l-138 139zM60 160l78 -78l78 78l-78 78zM394 293q57 -56 57 -135.5t-57 -135.5q-56 -56 -135 -56q-49 0 -93 24l32 31q29 -13 61 -13q62 0 105.5 44t43.5 105.5t-43.5 105.5t-105.5 44v-69l-91 90l91 90v-69q79 0 135 -56z" />
+<glyph unicode="&#xf305;" horiz-adv-x="451" d="M312 298l139 -138l-139 -139l-138 139zM390 160l-78 78l-78 -78l78 -78zM56 293q56 56 136 56v69l90 -90l-90 -90v69q-62 0 -105.5 -44t-43.5 -105.5t43.5 -105.5t105.5 -44q31 0 60 13l32 -31q-43 -24 -92 -24q-80 0 -136 56t-56 135.5t56 135.5z" />
+<glyph unicode="&#xf306;" horiz-adv-x="340" d="M65 266q-17 -24 -22 -53h-43q6 46 35 83zM43 171q5 -28 22 -53l-30 -30q-29 37 -35 83h43zM65 57l30 31q24 -17 53 -22v-43q-46 5 -83 34zM191 361q63 -8 106 -56t43 -113t-43 -113t-106 -56v43q45 8 75.5 43.5t30.5 82.5t-30.5 82.5t-75.5 43.5v-83l-98 95l98 97v-66z " />
+<glyph unicode="&#xf307;" horiz-adv-x="340" d="M246 330l-97 -95v83q-45 -8 -75.5 -43.5t-30.5 -82.5t30.5 -82.5t75.5 -43.5v-43q-63 8 -106 56t-43 113t43 113t106 56v66zM340 213h-43q-5 29 -22 53l30 30q29 -37 35 -83zM192 66q28 5 52 22l31 -31q-37 -28 -83 -34v43zM275 118q17 24 22 53h43q-6 -46 -35 -83z" />
+<glyph unicode="&#xf308;" horiz-adv-x="469" d="M235 96l-86 85h64v192h43v-192h64zM427 373q17 0 29.5 -12.5t12.5 -29.5v-299q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v299q0 17 12.5 29.5t30.5 12.5h128v-42h-128v-299h384v299h-128v42h128z" />
+<glyph unicode="&#xf309;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 29.5v86h43v-86h384v300h-384v-86h-43v85q0 18 12.5 30.5t30.5 12.5h384zM213 107v64h-213v42h213v64l86 -85z" />
+<glyph unicode="&#xf30a;" d="M341 363l86 -86h-64v-149q0 -35 -25 -60t-60.5 -25t-60.5 25t-25 60v149q0 18 -12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5v-149h64l-86 -85l-85 85h64v149q0 36 25 61t60.5 25t60.5 -25t25 -61v-149q0 -18 12.5 -30.5t30 -12.5t30 12.5t12.5 30.5v149h-64z" />
+<glyph unicode="&#xf30b;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM96 256h53v-85h43v85h53l-74 75zM331 128h-54v85h-42v-85h-54l75 -75z" />
+<glyph unicode="&#xf30c;" horiz-adv-x="299" d="M235 85h64l-86 -85l-85 85h64v150h43v-150zM85 384l86 -85h-64v-150h-43v150h-64z" />
+<glyph unicode="&#xf30d;" horiz-adv-x="384" d="M85 213v-64h150v-42h-150v-64l-85 85zM384 256l-85 -85v64h-150v42h150v64z" />
+<glyph unicode="&#xf30e;" d="M299 64l49 49l-105 104l-85 -85l-158 158l30 30l128 -128l85 85l135 -134l49 49v-128h-128z" />
+<glyph unicode="&#xf30f;" horiz-adv-x="405" d="M405 192l-85 -85v64h-320v42h320v64z" />
+<glyph unicode="&#xf310;" d="M299 320h128v-128l-49 49l-135 -134l-85 85l-128 -128l-30 30l158 158l85 -85l105 104z" />
+<glyph unicode="&#xf311;" horiz-adv-x="196" d="M0 51l98 98l98 -98l-30 -30l-68 68l-68 -68zM196 333l-98 -98l-98 98l30 30l68 -68l68 68z" />
+<glyph unicode="&#xf312;" horiz-adv-x="196" d="M98 324l-68 -68l-30 30l98 98l98 -98l-30 -30zM98 60l68 68l30 -30l-98 -98l-98 98l30 30z" />
+<glyph unicode="&#xf313;" horiz-adv-x="341" d="M0 277v86h85v-86h-85zM128 21v86h85v-86h-85zM0 21v86h85v-86h-85zM0 149v86h85v-86h-85zM128 149v86h85v-86h-85zM256 363h85v-86h-85v86zM128 277v86h85v-86h-85zM256 149v86h85v-86h-85zM256 21v86h85v-86h-85z" />
+<glyph unicode="&#xf314;" horiz-adv-x="485" d="M171 363h-31l-43 42h330q17 0 29.5 -12.5t12.5 -29.5v-330l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31zM341 363v-86h86v86h-86zM27 421l458 -458l-27 -27l-43 43h-330q-17 0 -29.5 12.5t-12.5 29.5v330l-43 43zM213 180v-31h31z M85 308v-31h31zM171 21v86h-86v-86h86zM171 149v74l-12 12h-74v-86h86zM299 21v74l-12 12h-74v-86h86zM341 21h31l-31 31v-31z" />
+<glyph unicode="&#xf315;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341zM128 21v86h-85v-86h85zM128 149v86h-85v-86h85zM128 277v86h-85v-86h85zM256 21v86h-85v-86h85zM256 149v86h-85v-86h85z M256 277v86h-85v-86h85zM384 21v86h-85v-86h85zM384 149v86h-85v-86h85zM384 277v86h-85v-86h85z" />
+<glyph unicode="&#xf316;" horiz-adv-x="405" d="M384 171q9 0 15 -6.5t6 -15.5v-128q0 -8 -6 -14.5t-15 -6.5h-363q-8 0 -14.5 6.5t-6.5 14.5v128q0 9 6.5 15.5t14.5 6.5h363zM384 384q9 0 15 -6.5t6 -14.5v-128q0 -9 -6 -15.5t-15 -6.5h-363q-8 0 -14.5 6.5t-6.5 15.5v128q0 8 6.5 14.5t14.5 6.5h363z" />
+<glyph unicode="&#xf317;" horiz-adv-x="363" d="M0 64v277h64v-277h-64zM299 341h64v-277h-64v277zM85 64v277h192v-277h-192z" />
+<glyph unicode="&#xf318;" d="M107 43v320h213v-320h-213zM0 85v235h85v-235h-85zM341 320h86v-235h-86v235z" />
+<glyph unicode="&#xf319;" horiz-adv-x="363" d="M128 64v277h107v-277h-107zM0 64v277h107v-277h-107zM256 341h107v-277h-107v277z" />
+<glyph unicode="&#xf31a;" horiz-adv-x="405" d="M0 256v85h85v-85h-85zM0 149v86h85v-86h-85zM107 149v86h85v-86h-85zM213 149v86h86v-86h-86zM107 256v85h85v-85h-85zM213 341h86v-85h-86v85zM320 149v86h85v-86h-85zM0 43v85h85v-85h-85zM107 43v85h85v-85h-85zM213 43v85h86v-85h-86zM320 43v85h85v-85h-85zM320 341 h85v-85h-85v85z" />
+<glyph unicode="&#xf31b;" horiz-adv-x="405" d="M0 43v149h128v-149h-128zM149 43v149h256v-149h-256zM0 341h405v-128h-405v128z" />
+<glyph unicode="&#xf31c;" horiz-adv-x="384" d="M0 171v213h171v-213h-171zM0 0v128h171v-128h-171zM213 0v213h171v-213h-171zM213 384h171v-128h-171v128z" />
+<glyph unicode="&#xf31d;" horiz-adv-x="405" d="M0 0v64h405v-64h-405zM384 277q9 0 15 -6t6 -15v-128q0 -9 -6 -15t-15 -6h-363q-8 0 -14.5 6t-6.5 15v128q0 9 6.5 15t14.5 6h363zM0 384h405v-64h-405v64z" />
+<glyph unicode="&#xf31e;" horiz-adv-x="341" d="M0 128v43h341v-43h-341zM0 43v42h341v-42h-341zM0 213v43h341v-43h-341zM0 341h341v-42h-341v42z" />
+<glyph unicode="&#xf31f;" horiz-adv-x="384" d="M0 171v42h43v-42h-43zM0 85v43h43v-43h-43zM0 256v43h43v-43h-43zM85 171v42h299v-42h-299zM85 85v43h299v-43h-299zM85 299h299v-43h-299v43z" />
+<glyph unicode="&#xf320;" horiz-adv-x="363" d="M0 149v86h85v-86h-85zM0 43v85h85v-85h-85zM0 256v85h85v-85h-85zM107 149v86h256v-86h-256zM107 43v85h256v-85h-256zM107 341h256v-85h-256v85z" />
+<glyph unicode="&#xf321;" horiz-adv-x="363" d="M0 213v128h107v-128h-107zM0 64v128h107v-128h-107zM128 64v128h107v-128h-107zM256 64v128h107v-128h-107zM128 213v128h107v-128h-107zM256 341h107v-128h-107v128z" />
+<glyph unicode="&#xf322;" horiz-adv-x="363" d="M128 64v128h107v-128h-107zM0 64v277h107v-277h-107zM256 64v128h107v-128h-107zM128 341h235v-128h-235v128z" />
+<glyph unicode="&#xf323;" horiz-adv-x="363" d="M0 64v128h363v-128h-363zM0 341h363v-128h-363v128z" />
+<glyph unicode="&#xf324;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM43 192v-43h85v43h-85zM256 64v43h-213v-43h213zM384 64v43h-85v-43h85zM384 149v43h-213v-43h213z" />
+<glyph unicode="&#xf325;" horiz-adv-x="384" d="M0 256v43h299v-43h-299zM0 171v42h299v-42h-299zM0 85v43h299v-43h-299zM341 85v43h43v-43h-43zM341 299h43v-43h-43v43zM341 171v42h43v-42h-43z" />
+<glyph unicode="&#xf326;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM277 64v85h-234v-85h234zM277 171v85h-234v-85h234zM384 64v192h-85v-192h85z" />
+<glyph unicode="&#xf327;" horiz-adv-x="405" d="M85 341q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-64q-8 0 -14.5 6t-6.5 15v256q0 9 6.5 15t14.5 6h64zM384 341q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-64q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h64zM235 341q8 0 14.5 -6t6.5 -15v-256q0 -9 -6.5 -15 t-14.5 -6h-64q-9 0 -15.5 6t-6.5 15v256q0 9 6.5 15t15.5 6h64z" />
+<glyph unicode="&#xf328;" horiz-adv-x="412" d="M213 171h171v-171h-171v171zM0 0v171h171v-171h-171zM0 384h171v-171h-171v171zM291 412l121 -121l-121 -120l-120 120z" />
+<glyph unicode="&#xf329;" d="M427 326l-28 -33l-98 83l28 32zM125 376l-97 -82l-28 32l98 82zM213.5 363q79.5 0 135.5 -56.5t56 -136t-56 -135.5t-135.5 -56t-136 56t-56.5 135.5t56.5 136t136 56.5zM213 21q62 0 106 44t44 106t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44z M182 138l105 106l23 -23l-128 -128l-68 68l23 22z" />
+<glyph unicode="&#xf32a;" horiz-adv-x="434" d="M221 320q-26 0 -51 -9l-33 32q40 20 84 20q79 0 135.5 -56.5t56.5 -135.5q0 -44 -20 -84l-32 32q9 26 9 52q0 62 -43.5 105.5t-105.5 43.5zM434 326l-27 -33l-99 83l28 32zM27 399l21 -21l372 -372l-27 -27l-47 47q-54 -47 -125 -47q-80 0 -136 56t-56 136q0 71 47 125 l-17 17l-24 -20l-30 31l23 19l-28 29zM316 56l-210 210q-35 -42 -35 -95q0 -62 44 -106t106 -44q54 0 95 35zM136 378l-18 -15l-31 30l19 15z" />
+<glyph unicode="&#xf32b;" d="M125 376l-97 -82l-28 32l98 82zM427 326l-28 -33l-98 83l28 32zM213.5 363q79.5 0 135.5 -56.5t56 -136t-56 -135.5t-135.5 -56t-136 56t-56.5 135.5t56.5 136t136 56.5zM213 21q62 0 106 44t44 106t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44z M235 256v-64h64v-43h-64v-64h-43v64h-64v43h64v64h43z" />
+<glyph unicode="&#xf32c;" d="M125 376l-97 -82l-28 32l98 82zM427 326l-28 -33l-98 83l28 32zM213.5 363q79.5 0 135.5 -56.5t56 -136t-56 -135.5t-135.5 -56t-136 56t-56.5 135.5t56.5 136t136 56.5zM213 21q62 0 106 44t44 106t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44z M149 213v43h128v-38l-77 -90h77v-43h-128v39l78 89h-78z" />
+<glyph unicode="&#xf32d;" d="M427 326l-28 -33l-98 83l28 32zM125 376l-97 -82l-28 32l98 82zM224 277v-112l85 -50l-16 -26l-101 60v128h32zM213.5 363q79.5 0 135.5 -56.5t56 -136t-56 -135.5t-135.5 -56t-136 56t-56.5 135.5t56.5 136t136 56.5zM213 21q62 0 106 44t44 106t-44 105.5t-106 43.5 t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44z" />
+<glyph unicode="&#xf32e;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43h43v-43h21zM341 43v234h-298v-234h298zM85 235h107v-107h-107v107z" />
+<glyph unicode="&#xf32f;" horiz-adv-x="384" d="M289 212l-127 -127l-68 68l23 23l45 -45l104 104zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43h43v-43h21zM341 43v234h-298v-234h298z" />
+<glyph unicode="&#xf330;" horiz-adv-x="384" d="M135 85l-23 23l52 52l-52 52l23 23l52 -52l52 52l22 -23l-52 -52l52 -52l-22 -23l-52 52zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43h43v-43h21z M341 43v234h-298v-234h298z" />
+<glyph unicode="&#xf331;" horiz-adv-x="384" d="M299 235v-43h-214v43h214zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43h43v-43h21zM341 43v234h-298v-234h298zM235 149v-42h-150v42h150z" />
+<glyph unicode="&#xf332;" horiz-adv-x="384" d="M299 192v-107h-107v107h107zM277 427h43v-43h21q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h21v43h43v-43h170v43zM341 43v234h-298v-234h298z" />
+<glyph unicode="&#xf333;" horiz-adv-x="384" d="M171 85.5q0 8.5 6 15t15 6.5t15 -6.5t6 -15t-6 -15t-15 -6.5t-15 6.5t-6 15zM171 384h21q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136q0 46 20.5 86.5t56.5 66.5v1l145 -145l-30 -30l-116 115q-33 -41 -33 -94q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5 t43.5 105.5q0 56 -36.5 98t-91.5 50v-41h-42v85zM320 192q0 -9 -6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6t15 -6t6.5 -15zM64 192q0 9 6.5 15t15 6t15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15z" />
+<glyph unicode="&#xf334;" d="M303.5 282.5q37.5 -37.5 37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90 38l90 90v128q53 0 90.5 -37.5zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50 t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf335;" horiz-adv-x="448" d="M299 192q0 -18 -12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5t12.5 30.5t30.5 12.5t30.5 -12.5t12.5 -30.5zM256 384q80 0 136 -56t56 -136t-56 -136t-136 -56q-65 0 -117 40l30 30q40 -27 87 -27q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5 t-43.5 -105.5h64l-86 -85l-85 85h64q0 80 56 136t136 56z" />
+<glyph unicode="&#xf336;" horiz-adv-x="448" d="M256 384q80 0 136 -56t56 -136t-56 -136t-136 -56q-79 0 -136 56l31 31q43 -44 105 -44t105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5h64l-87 -86l-1 3l-83 83h64q0 80 56 136t136 56zM235 277h32v-90l74 -45l-15 -26l-91 55v106z" />
+<glyph unicode="&#xf337;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM224 299v-112l96 -57l-16 -27l-112 68v128h32z" />
+<glyph unicode="&#xf338;" horiz-adv-x="411" d="M369 351l30 -30l-30 -31q42 -52 42 -119q0 -58 -32 -106l-31 31q20 35 20 75q0 62 -43.5 105.5t-105.5 43.5q-40 0 -75 -20l-31 31q48 32 106 32q67 0 120 -42zM283 427v-43h-128v43h128zM197 247v30h43v-73zM27 363l214 -214l164 -165l-27 -27l-53 54q-48 -32 -106 -32 q-80 0 -136 56t-56 136q0 58 32 106l-59 59zM219 21q40 0 75 21l-204 204q-21 -35 -21 -75q0 -62 44 -106t106 -44z" />
+<glyph unicode="&#xf339;" horiz-adv-x="384" d="M256 427v-43h-128v43h128zM171 149v128h42v-128h-42zM342 290q42 -52 42 -119q0 -80 -56 -136t-136 -56t-136 56t-56 135.5t56 136t136 56.5q67 0 120 -43l30 31q16 -13 30 -30zM192 21q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5 t43.5 -106t105.5 -44z" />
+<glyph unicode="&#xf33a;" horiz-adv-x="299" d="M0 107v85h299v-85q0 -62 -44 -106t-106 -44t-105.5 44t-43.5 106zM237 355q29 -21 45.5 -52.5t16.5 -67.5v-22h-299v22q0 36 16.5 67.5t44.5 52.5l-44 45l17 17l49 -49q32 16 66 16t66 -16l50 49l17 -17zM85.5 256q8.5 0 15 6.5t6.5 15t-6.5 15t-15 6.5t-15 -6.5 t-6.5 -15t6.5 -15t15 -6.5zM213.5 256q8.5 0 15 6.5t6.5 15t-6.5 15t-15 6.5t-15 -6.5t-6.5 -15t6.5 -15t15 -6.5z" />
+<glyph unicode="&#xf33b;" d="M85 64v213h256v-213q0 -9 -6 -15t-15 -6h-21v-75q0 -13 -9.5 -22.5t-23 -9.5t-22.5 9.5t-9 22.5v75h-43v-75q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5v75h-21q-9 0 -15.5 6t-6.5 15zM32 277q13 0 22.5 -9t9.5 -23v-149q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5 t-9.5 22.5v149q0 14 9.5 23t22.5 9zM394.5 277q13.5 0 23 -9t9.5 -23v-149q0 -13 -9.5 -22.5t-23 -9.5t-22.5 9.5t-9 22.5v149q0 14 9 23t22.5 9zM289 402q52 -38 52 -103h-256q0 64 53 103l-28 28q-8 7 -0.5 14.5t15.5 0.5l32 -32q26 14 56 14t57 -14l31 32q8 7 15.5 -0.5 t-0.5 -14.5zM171 341v22h-22v-22h22zM277 341v22h-21v-22h21z" />
+<glyph unicode="&#xf33c;" horiz-adv-x="363" d="M353 262q-21 -7 -35 -32.5t-14 -50.5q0 -31 16 -57.5t43 -33.5q-8 -27 -26.5 -55.5t-37.5 -42.5q-16 -11 -40 -11q-16 0 -37 8q-18 9 -31 9q-10 0 -40 -12q-18 -5 -26 -5q-24 0 -49 20q-36 34 -56 81t-20 98q0 53 30.5 93.5t77.5 40.5q26 0 48 -11q17 -11 34 -11 q16 0 31 6q39 16 52 16q35 0 61 -23q12 -12 19 -27zM179 309q0 32 25 63q25 27 61 33q0 -38 -24 -67q-27 -29 -62 -29z" />
+<glyph unicode="&#xf33d;" d="M384 281h-107v26h107v-26zM208 180.5q12 -17.5 12 -42.5q0 -20 -8 -35q-7 -14 -21 -23q-12 -9 -30 -14q-14 -4 -34 -4h-127v266h124q12 0 34 -5q13 -3 26 -12q11 -7 18 -20q6 -13 6 -31q0 -20 -9.5 -33.5t-26.5 -21.5q24 -7 36 -24.5zM55 221h61q17 0 26 6q10 7 10 23 q0 9 -3.5 15t-9.5 9q-6 4 -12 5q-9 2 -15 2h-57v-60zM162 141q0 20 -11 29q-11 8 -30 8h-66v-73h64q7 0 17 2q8 2 13.5 5.5t9.5 11.5q3 6 3 17zM426 144h-137q0 -24 13 -37q12 -11 34 -11q15 0 27 8q12 9 14 18h46q-10 -35 -34 -50q-24 -16 -55 -16q-22 0 -40 7t-31 21 q-13 13 -19 32q-7 18 -7 40t7 40.5t20 32.5q13 13 30 21q18 8 40 8q24 0 42 -9.5t30 -25.5q11 -15 17 -37q5 -21 3 -42zM374 178q-2 18 -12 30q-9 10 -29 10q-13 0 -21 -4.5t-13.5 -10.5t-6.5 -13q-3 -7 -3 -12h85z" />
+<glyph unicode="&#xf33e;" d="M390 161v62l-46 -31zM232 31l143 96l-64 43l-79 -53v-86zM213 149l65 43l-65 43l-65 -43zM195 31v86l-80 53l-64 -43zM37 223v-62l46 31zM195 353l-144 -96l64 -43l80 53v86zM232 353v-86l79 -53l64 43zM427 259v-2v-130v-2v-1q0 -1 -1 -2v-1q-1 0 -1 -1v-1l-1 -1v-1 l-0.5 -0.5l-0.5 -0.5q0 -1 -1 -1l-1 -1v0l-1 -1l-1 -1l-195 -130q-5 -3 -10.5 -3t-10.5 3l-195 130h-1v1l-1 0.5l-1 0.5v1h-1v1l-1 1v1h-1v1l-1 1v1v1q-1 1 -1 2v1v2v130v2v1q0 1 1 2v1v1l1 1v1l1 1l0.5 0.5l0.5 0.5v1q1 0 1 0.5v0.5h0.5t0.5 1h1l1 1l195 130q10 7 21 0 l195 -130v0l1 -1h1v-1q1 0 1 -1q1 0 1 -0.5v-0.5l1 -1v-1q1 0 1 -1v-1l1 -1v-1q1 -1 1 -2v-1z" />
+<glyph unicode="&#xf33f;" d="M308 42q56 40 69 107q-35 8 -66 8v0q-17 0 -34 -3q19 -57 31 -112zM213 13q31 0 59 11q-12 63 -32 121q-49 -16 -87 -52q-23 -22 -39 -47q44 -33 99 -33zM47 179q0 -60 39 -106q19 28 46 53q42 38 94 55q-4 10 -10 22q-67 -21 -151 -22q-13 0 -18 1v-3zM140 329 q-33 -16 -56 -45t-32 -64q3 -1 13 -1h3q70 0 131 19q-29 54 -59 91zM213 346q-16 0 -35 -4q32 -42 57 -91q53 23 82 58q-45 37 -104 37zM344 282q-36 -41 -92 -66q4 -8 11 -25q24 4 48 4v0q33 0 69 -8q-3 53 -36 95zM213.5 393q88.5 0 151 -62.5t62.5 -151t-62.5 -151 t-151 -62.5t-151 62.5t-62.5 151t62.5 151t151 62.5z" />
+<glyph unicode="&#xf340;" d="M126 389l87 -72l88 72l126 -81l-87 -69l87 -69l-126 -82l-88 73l-87 -73l-126 82l87 69l-87 69zM213 317l-126 -78l126 -78l127 78zM213 145l89 -73l37 25v-27l-126 -75l-125 75v27l38 -25z" />
+<glyph unicode="&#xf341;" horiz-adv-x="366" d="M249 200h50q3 10 -8 21q-12 12 -27 3.5t-15 -24.5zM332 348q11 -14 17.5 -34.5t8 -32t4.5 -38.5q4 -39 3.5 -88.5t-10.5 -87.5q-9 -61 -49 -80.5t-95 -4.5q-22 6 -32 27t-7 44q4 21 24 31.5t43 10.5v-21q2 -7 -1 -9.5t-8.5 -2t-11.5 -0.5q-8 -5 -9 -16.5t8.5 -21 t27.5 -9.5q33 1 40 12t5 48q2 19 -14 32t-36 14q-37 -3 -65 43q-1 -2 -1 -10.5v-16.5v-15q-1 -15 -15 -23.5t-31 -11.5q-60 -5 -84 19q-34 36 -43 120q-7 48 22 69h81q4 2 10.5 9.5t7.5 8.5v43q1 4 0.5 14.5t1 17t6.5 11.5q22 11 47 4t38 -28h27h28q43 -6 62 -27zM87 313 h-69l86 88v-70z" />
+<glyph unicode="&#xf342;" d="M363 320h-54q-31 0 -52.5 -22t-21.5 -53v-53h-43v-64h43v-149h64v149h64v64h-64v43q0 8 6 14.5t15 6.5h43v64zM0 405h427v-426h-427v426z" />
+<glyph unicode="&#xf343;" horiz-adv-x="224" d="M145 -21h-79v194h-66v76h66v56q0 48 27 74t72 26q36 0 59 -3v-67l-41 -1q-22 0 -30 -9t-8 -27v-49h76l-10 -76h-66v-194z" />
+<glyph unicode="&#xf344;" d="M43 405h341q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-110q-7 1 -7 21v58q0 27 -15 40q44 5 70.5 27t26.5 78q0 33 -22 57q11 26 -2 57q-18 6 -58 -22q-26 7 -54 7t-53 -7q-18 12 -32.5 17.5t-20.5 4.5h-6q-12 -31 -2 -57q-22 -24 -22 -57 q0 -55 27 -77.5t70 -27.5q-11 -10 -13 -29q-42 -18 -62 18q-12 20 -33 22q-2 0 -4.5 -0.5t-5.5 -3.5t8 -9q15 -7 24 -31q1 -2 2 -4.5t6.5 -9.5t13 -10.5t20.5 -6.5t30 2v-36q0 -20 -8 -21h-109q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5z" />
+<glyph unicode="&#xf345;" d="M213.5 400q88.5 0 151 -62.5t62.5 -150.5q0 -70 -41 -125.5t-105 -77.5q-14 -2 -14 11v58q0 27 -15 40q44 5 70.5 27t26.5 77q0 34 -22 58q11 26 -2 57q-18 5 -58 -22q-26 7 -54 7t-53 -7q-18 12 -32.5 17.5t-20.5 4.5h-6q-12 -31 -2 -57q-22 -24 -22 -58q0 -55 27 -77 t70 -27q-11 -10 -13 -29q-42 -18 -62 18q-12 20 -33 22q-2 0 -4.5 -0.5t-5 -3.5t8.5 -9q14 -7 23 -31q1 -2 2 -4.5t6.5 -9.5t13 -10.5t20.5 -6.5t30 2v-36q0 -13 -14 -11q-64 22 -105 77.5t-41 125.5q0 88 62.5 150.5t151 62.5z" />
+<glyph unicode="&#xf346;" horiz-adv-x="463" d="M140 373l73 -128l-140 -245l-73 128zM183 128h280l-73 -128h-280zM451 149h-146l-147 256h1h145z" />
+<glyph unicode="&#xf347;" d="M222 287q114 -108 165 -114q1 11 1 19q0 25 -7 50q-4 -9 -11 -10t-15.5 5.5t-15.5 14.5t-14.5 18.5t-10 15t-3.5 6.5q-47 66 -163 62q-32 -13 -56 -36q65 30 130 -31zM365 105q11 20 16 39q-33 3 -85.5 29.5t-87.5 51.5l-35 25q-74 58 -127 -9q-8 -24 -8 -49 q0 -38 16 -73q9 26 25 26q15 0 40.5 -13.5t41.5 -18.5q10 -3 31 -10l31.5 -10.5t26.5 -6.5t30 -3q12 0 22 1.5t20 4.5t15.5 4.5t15.5 6t12 5.5zM213 17q76 0 128 56q-45 -13 -83.5 -13t-62.5 7l-25 8q-26 8 -31 -6t7 -38q32 -14 67 -14zM213 405q88 0 151 -62.5t63 -150.5 t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+<glyph unicode="&#xf348;" horiz-adv-x="416" d="M235 213h181v-21q0 -89 -58 -151t-145 -62q-88 0 -150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5q89 0 148 -65l-38 -38q-43 50 -110 50q-66 0 -113 -47t-47 -113t47 -113t113 -47q56 0 96.5 36t50.5 92h-125v53z" />
+<glyph unicode="&#xf349;" horiz-adv-x="448" d="M341 427q44 0 75.5 -31.5t31.5 -75.5q0 -22 -26.5 -67.5t-52 -92.5t-22.5 -75q0 -5 -5.5 -5t-5.5 5q2 28 -23 75t-51.5 92.5t-26.5 67.5q0 44 31 75.5t75 31.5zM341.5 363q-17.5 0 -30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5t30 12.5t12.5 30.5t-12.5 30.5t-30 12.5z M43 384h185q-20 -32 -20 -69q0 -26 32 -83l-239 -239l-1 7v341q0 18 12.5 30.5t30.5 12.5zM310 109l-51 51l14 15q24 -39 37 -66zM371 -43h-315l157 158zM427 205v-205l-1 -7l-72 72q3 9 7 18.5t9 20t9.5 19t12 21.5t11 19.5t12.5 21.5zM100 181q-17 0 -27 7t-10 19 q0 14 18 21q10 3 22 3h5q13 -10 18 -15t5 -12q0 -9 -9 -16t-22 -7zM75 303q0 10 5.5 15.5t12.5 5.5q13 0 20.5 -12t7.5 -25q0 -11 -6.5 -15.5t-13.5 -4.5q-11 0 -18.5 11.5t-7.5 24.5zM127 241l-7 6q-6 5 -6 9q0 7 7 12q17 13 17 29q0 14 -14 26h12l9 9h-43 q-21 0 -32.5 -11.5t-11.5 -27.5q0 -13 9 -23t25 -10h5l-2 -8q0 -7 6 -14q-24 -1 -40 -11q-16 -9 -16 -25q0 -13 11.5 -21.5t33.5 -8.5q25 0 39.5 12t14.5 27q0 16 -17 30z" />
+<glyph unicode="&#xf34a;" horiz-adv-x="384" d="M0 341q0 18 12.5 30.5t30.5 12.5h128v-107l-86 22l22 -86h-107v128zM107 171l-22 -86l86 22v-107h-128q-18 0 -30.5 12.5t-12.5 30.5v128h107zM299 85l-22 86h107v-128q0 -18 -12.5 -30.5t-30.5 -12.5h-128v107zM341 384q18 0 30.5 -12.5t12.5 -30.5v-128h-107l22 86 l-86 -22v107h128z" />
+<glyph unicode="&#xf34b;" horiz-adv-x="379" d="M0 11v362q0 21 18 29l210 -210l-210 -210q-18 9 -18 29zM295 125l-230 -132l181 181zM366 217q13 -10 13 -25t-12 -25l-49 -28l-54 53l54 53zM65 391l230 -132l-49 -49z" />
+<glyph unicode="&#xf34c;" d="M43 405h340q17 0 30.5 -17t13.5 -36v-330q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v341q0 18 12.5 30.5t30.5 12.5zM151 301q-45 0 -76.5 -32t-31.5 -77t31.5 -77t76.5 -32q47 0 75.5 29.5t28.5 76.5q0 13 -2 19h-102v-38h62q-3 -17 -18 -31.5 t-44 -14.5q-28 0 -47.5 20t-19.5 48t19.5 48t47.5 20q27 0 43 -16l30 28q-29 29 -73 29zM322 239v-31h-31v-31h31v-31h31v31h30l1 31h-31v31h-31z" />
+<glyph unicode="&#xf34d;" d="M137 217h128q7 -37 -3 -72q-10 -34 -35 -57q-23 -21 -56 -29q-36 -8 -70 1q-27 7 -49 25q-24 19 -37 45q-22 42 -12 89q3 18 12 34q24 50 77 68q46 16 92 -1q24 -9 44 -27q-2 -3 -7 -7.5t-6 -6.5q-4 -3 -12.5 -11.5t-12.5 -13.5q-13 13 -30 18q-20 6 -40 1 q-24 -5 -41 -22q-13 -14 -20 -33q-9 -26 0 -53q9 -26 32 -42q14 -10 30 -13q15 -3 33 0q17 3 30 12q23 15 27 42h-74v26.5v26.5zM427 214v-34h-47v-46h-34v46h-47v34h47v47h34v-47h47z" />
+<glyph unicode="&#xf34e;" horiz-adv-x="418" d="M214 222v1h201q3 -12 3 -36q0 -93 -56.5 -150.5t-148.5 -57.5q-88 0 -150.5 62t-62.5 151t62 151t151 62q87 0 144 -57l-57 -56q-33 33 -86 33q-54 0 -92.5 -39.5t-38.5 -95t38.5 -94.5t92.5 -39q31 0 55 9.5t37.5 24.5t20.5 29.5t10 27.5h-123v74z" />
+<glyph unicode="&#xf34f;" d="M384 309v43q0 11 -11 11h-42q-11 0 -11 -11v-43q0 -10 11 -10h42q11 0 11 10zM53 21h320q11 0 11 11v181h-45q2 -12 2 -21q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 11 2 21h-44v-181q0 -11 10 -11zM213.5 277q-35.5 0 -60.5 -25t-25 -60t25 -60t60.5 -25 t60.5 25t25 60t-25 60t-60.5 25zM384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341z" />
+<glyph unicode="&#xf350;" horiz-adv-x="401" d="M59 354h342l-31 -156l-5 -25l-24 -121l-183 -61l-158 61l16 80h67l-6 -33l95 -36l111 36l15 77h-274l13 67h274l9 44h-274z" />
+<glyph unicode="&#xf351;" horiz-adv-x="357" d="M179 50l91 25l13 138h-163l-4 45h171l4 45h-225l13 -135h155l-5 -58l-50 -14l-50 14l-4 37h-45l7 -72zM0 378h357l-32 -365l-146 -51l-147 51z" />
+<glyph unicode="&#xf352;" horiz-adv-x="384" d="M0 384h384v-384h-384v384zM101 63q15 -33 54 -33q25 0 39.5 13.5t14.5 40.5v124h-36v-123q0 -23 -19 -23q-13 0 -24 19zM228 67q19 -37 66 -37q27 0 43.5 13.5t16.5 36.5q0 22 -11.5 34t-36.5 23l-9 4q-12 5 -17 9.5t-5 12.5q0 6 4.5 10.5t12.5 4.5q15 0 24 -15l27 18 q-16 29 -51 29q-24 0 -38.5 -13.5t-14.5 -34.5t11 -33t33 -21l9 -4q10 -5 14.5 -7t8 -6.5t3.5 -10.5q0 -8 -6.5 -13t-17.5 -5q-23 0 -36 22z" />
+<glyph unicode="&#xf353;" horiz-adv-x="458" d="M0 294q45 29 82 35.5t60 -5.5t39 -35.5t23 -48t8 -49.5q3 -37 -18.5 -72.5t-57.5 -47.5t-83 16v-120l-53 34v293zM51 256v-121q41 -25 65.5 -21t35 24.5t10.5 56.5q0 47 -17 68t-41.5 17.5t-52.5 -24.5zM299 328q-4 -78 0 -155q3 -21 14.5 -30.5t26.5 -8t30 6t25 10.5 l10 5v155l53 -6v-207q0 -28 -8 -50.5t-20 -36t-27 -23t-30.5 -13.5t-27.5 -6t-20 -2h-8l-18 51q35 0 59 8.5t33 20t13.5 23.5t3.5 20l-1 8q-42 -16 -73.5 -17.5t-47.5 7.5t-25.5 20.5t-11.5 20.5l-2 10v155z" />
+<glyph unicode="&#xf354;" d="M366 288q25 0 43 -18t18 -43v-81q0 -25 -18 -43t-43 -18h-153q0 -6 5 -13t10 -7h92v-36q0 -25 -18 -43t-43 -18h-91q-26 0 -43.5 18t-17.5 43v80q0 25 17.5 43t43.5 18h112q25 0 42.5 18t17.5 43v57h26zM274 36q-15 0 -15 -19q0 -15 15 -15q7 0 11 4.5t4 10.5 q0 19 -15 19zM61 75q-25 0 -43 17.5t-18 43.5v80q0 26 18 43.5t43 17.5h152q0 7 -4.5 14t-10.5 7h-91v36q0 25 17.5 43t43.5 18h91q25 0 43 -18t18 -43v-80q0 -26 -18 -43.5t-43 -17.5h-112q-25 0 -43 -18t-18 -43v-57h-25zM152 326q16 0 16 19q0 15 -16 15q-15 0 -15 -15 q0 -19 15 -19z" />
+<glyph unicode="&#xf355;" d="M325 72q-58 0 -87 22.5t-42 64.5l-16 49q-11 32 -25 48t-44 16q-25 0 -42.5 -20t-17.5 -62q0 -35 16 -56t42 -21q17 0 33 7t23 14l8 7l15 -43q-3 -3 -9 -7t-27 -11.5t-45 -7.5q-52 0 -79.5 30t-27.5 86q0 59 28.5 91.5t81.5 32.5q49 0 76 -20t42 -68l16 -50 q10 -30 28.5 -46t53.5 -16q51 0 51 26q0 23 -33 30l-34 8q-56 14 -56 65q0 38 24.5 54.5t62.5 16.5q78 0 84 -63l-49 -6q-3 30 -38 30t-35 -26q0 -23 28 -29l31 -7q65 -15 65 -71q0 -68 -102 -68z" />
+<glyph unicode="&#xf356;" d="M363 43v121q0 31 -22 53t-53 22q-15 0 -30 -8.5t-23 -21.5v26h-64v-192h64v113q0 13 9 22.5t22.5 9.5t23 -9.5t9.5 -22.5v-113h64zM96 271q16 0 27.5 11t11.5 27t-11.5 27.5t-27.5 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27t27.5 -11zM128 43v192h-64v-192h64zM384 405 q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341z" />
+<glyph unicode="&#xf357;" horiz-adv-x="371" d="M237 405q79 0 112 -39q30 -35 20 -99q-23 -146 -175 -146h-49q-8 0 -14 -5t-7 -13l-17 -106q-1 -8 -7 -13t-14 -5h-73q-6 0 -10 4.5t-3 9.5l62 394q2 8 7.5 13t13.5 5h154zM255 261q4 29 -8 43q-6 8 -18 11.5t-21.5 4t-27.5 0.5h-11q-11 0 -12 -11l-17 -103h23 q17 0 25.5 0.5t22 3.5t21 8.5t14 16.5t9.5 26z" />
+<glyph unicode="&#xf358;" d="M235 102q53 0 82 35t29 82q0 52 -39 89.5t-93.5 37.5t-93.5 -37.5t-39 -89.5q0 -34 18 -63q6 -11 18 -11q9 0 15.5 6.5t6.5 14.5q0 5 -4 11q-11 20 -11 42q0 35 26 59.5t63 24.5t63.5 -24.5t26.5 -59.5q0 -30 -16.5 -51.5t-51.5 -21.5q-12 0 -20 8.5t-8 20.5 q0 9 9.5 28.5t9.5 35.5q0 28 -31 28q-14 0 -24.5 -11.5t-10.5 -36.5q0 -8 1 -16t2 -12l1 -3l-39 -119l-1 -4v-1.5v-1.5q0 -10 6.5 -17t16.5 -7q14 0 20 12l1 -1l1 4l20 69q19 -20 46 -20zM384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341 q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341z" />
+<glyph unicode="&#xf359;" d="M427 332v-37v-75q0 -39 -10 -69q-18 -60 -68 -102q-53 -44 -121 -48q-70 -5 -129 32q-54 35 -80 93q-15 33 -18 66q-1 18 -1 75v36.5v38.5q0 14 7.5 25t20.5 15q8 2 16 2h20h38h74h21h16q25 0 75.5 -0.5t75.5 -0.5q27 0 35 -2q14 -4 22 -17q6 -9 6 -32zM342 235 q5 15 -6 27q-10 13 -27 10q-5 0 -9.5 -3t-7 -5t-8 -7.5l-6.5 -6.5q-56 -55 -64 -62q-2 1 -56 53q-7 7 -15 14q-11 11 -14 13q-13 9 -27 2q-15 -6 -17.5 -21.5t8.5 -26.5q1 0 58 -56l28 -26q1 -2 5.5 -6.5t7 -6.5t7 -5t8.5 -4q15 -3 27 8q4 4 9 8.5t11 10.5l9 9q52 50 58 55 l5.5 5.5l6.5 6.5t5 6t4 8z" />
+<glyph unicode="&#xf35a;" horiz-adv-x="491" d="M395 363l96 -171l-96 -171h-86l96 171l-55 99l-169 -270h-85l-96 171l96 171h85l-96 -171l56 -99l168 270h86z" />
+<glyph unicode="&#xf35b;" horiz-adv-x="384" d="M320 105q26 0 44 -18.5t18 -44t-18 -44t-44 -18.5t-44 18.5t-18 44.5q0 6 1 14l-151 88q-19 -17 -44 -17q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5q25 0 44 -17l150 87q-2 9 -2 15q0 27 18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45t-18.5 -45.5t-45.5 -19 q-25 0 -44 18l-150 -88q2 -9 2 -15t-2 -15l152 -88q18 16 42 16z" />
+<glyph unicode="&#xf35c;" horiz-adv-x="336" d="M245 13v145h34v-179h-279v178l32 -1l-1 -143h214zM52 73h167v-35h-167v35zM57 136l168 -16l-4 -36l-168 16zM72 209l163 -46l-10 -35l-163 46zM112 291l144 -87l-19 -32l-144 87zM262 210l-98 137l30 21l98 -137zM272 399l36 6l28 -166l-36 -6z" />
+<glyph unicode="&#xf35d;" horiz-adv-x="439" d="M355 263q0 23 -16.5 39t-39 16t-39 -16t-16.5 -39t16.5 -39t39 -16t39 16t16.5 39zM181 95q0 -24 -17 -40t-40 -16q-16 0 -29.5 8t-20.5 22q15 -6 28 -12q17 -6 34 1t25 25q6 17 -1 34t-25 24l-23 9q6 2 12 2q23 0 40 -16.5t17 -40.5zM439 329v-274q0 -34 -24 -58 t-58 -24h-275q-34 0 -58 24t-24 58v44l49 -20q6 -26 27 -43.5t48 -17.5q30 0 52 20t25 50l98 72q43 0 73 30t30 73q0 42 -30 72.5t-73 30.5q-42 0 -72 -30t-31 -72l-64 -92h-8q-21 0 -39 -11l-85 34v134q0 34 24 58t58 24h275q34 0 58 -24t24 -58zM368 263q0 -29 -20 -49 t-48.5 -20t-49 20t-20.5 48.5t20.5 49t48.5 20.5q29 0 49 -20.5t20 -48.5z" />
+<glyph unicode="&#xf35e;" d="M372 273q0 -26 -18 -44.5t-44 -18.5t-44.5 18.5t-18.5 44.5t18.5 44.5t44.5 18.5t44 -18.5t18 -44.5zM0 73v110l65 -26q20 12 45 12h9l73 105q0 48 34.5 82t82.5 34q49 0 83.5 -34.5t34.5 -83t-34.5 -83t-83.5 -34.5l-112 -82q-3 -34 -28 -56.5t-59 -22.5q-32 0 -56 19.5 t-30 49.5zM309.5 352q-32.5 0 -55.5 -23.5t-23 -56t23 -55.5t55.5 -23t55.5 23t23 55.5t-23 56t-55.5 23.5zM110 146q-7 0 -14 -2l27 -10q19 -8 27.5 -27.5t0.5 -39.5t-27.5 -28t-39.5 -1q-6 3 -16.5 7.5t-14.5 5.5q18 -34 57 -34q26 0 45 19t19 45.5t-19 45.5t-45 19z" />
+<glyph unicode="&#xf35f;" d="M335 249q22 18 28 30q-13 -6 -31 -9q18 13 24 32q-20 -11 -37 -14q-12 14 -31 16.5t-35.5 -5t-26.5 -25t-5 -38.5q-67 4 -118 59q-11 -20 -4.5 -43.5t21.5 -32.5q-11 1 -24 7q1 -43 44 -57q-12 -3 -24 -1q12 -36 53 -40q-15 -13 -39 -19.5t-45 -3.5q45 -28 92 -26 q70 3 113.5 49.5t44.5 120.5zM384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341z" />
+<glyph unicode="&#xf360;" d="M383 279v-11q0 -45 -16.5 -88.5t-47 -79.5t-79 -58.5t-106.5 -22.5q-73 0 -134 39q10 -1 21 -1q61 0 109 37q-29 1 -51.5 18t-30.5 43q8 -2 16 -2q12 0 23 4q-30 6 -50 30t-20 55v1q19 -10 40 -11q-39 27 -39 73q0 24 12 44q33 -40 79.5 -64t100.5 -27q-2 10 -2 20 q0 36 25.5 61.5t61.5 25.5q38 0 64 -27q30 6 56 21q-10 -31 -39 -48q27 3 51 13q-18 -26 -44 -45z" />
+<glyph unicode="&#xf361;" horiz-adv-x="549" d="M548 299q7 -18 -43 -84q-7 -9 -18 -24q-23 -28 -26 -37q-5 -12 4 -23q5 -6 23 -24h1v-1q41 -37 55 -63l2 -4t2 -7.5t0 -9.5t-7 -7.5t-17 -3.5l-73 -2q-7 -1 -16.5 2t-14.5 6l-6 4q-9 6 -20 18t-19.5 22t-17.5 16.5t-16 4.5q-1 0 -2.5 -1t-5 -4.5t-6 -8.5t-4.5 -14.5 t-2 -22.5q0 -4 -1 -7.5t-2 -5.5l-1 -1q-6 -6 -16 -6h-32q-21 -2 -42.5 4t-37.5 15.5t-29 19t-20 16.5l-7 7q-3 2 -8 8t-20.5 26t-30.5 43t-35 60.5t-37 77.5q-2 5 -2 8t1 5l1 1q4 6 16 6h79q3 0 6 -1.5l5 -2.5l1 -1q5 -3 7 -9q6 -14 13.5 -29.5t11.5 -23.5l4 -8 q9 -17 16.5 -29.5t13.5 -19.5t12 -11t10 -4t8 1l1 1.5t3.5 6.5t4 13t2.5 23t0 36q-1 11 -3 20.5t-4 13.5l-1 3q-7 10 -25 13q-3 0 2 7q5 5 11 8q15 8 68 7q23 0 39 -4q5 -1 9 -3.5t6 -7t3 -9t1 -13v-15.5q-1 -8 -1 -20v-24q0 -3 -0.5 -12t-0.5 -14t1 -11.5t3.5 -11t6.5 -6.5 q2 -1 4.5 -1.5t7.5 3t11 10t15 19.5t19 30q17 30 31 65q1 2 2.5 4.5t3.5 3.5h1l1 1l4 1h6l82 1q11 1 18.5 -1t8.5 -5z" />
+<glyph unicode="&#xf362;" d="M40 280q-17 29 -38 37l-2 1v15h1h109v-15q-13 -1 -21.5 -7t-5.5 -17q14 -33 40.5 -94t38.5 -89l46 87q-7 14 -23 51.5t-27 58.5q-7 10 -36 11v14h102l1 -14q-6 -1 -10 -2t-7 -4.5t-2 -8.5l29 -64q28 60 28 61q3 11 -5 14.5t-21 3.5l-1 14h92v-14q-24 -2 -33 -15 q-14 -20 -46 -89q23 -53 43 -95l78 180q-6 13 -29 19l-1 14l87 -1v-14q-6 -1 -11 -3q-11 -5 -18 -17l-107 -247h-18l-52 120l-62 -120h-18q-16 33 -48 111t-53 118z" />
+<glyph unicode="&#xf363;" horiz-adv-x="363" d="M0 192v112l128 28v-138zM363 384v-187l-214 -3v143zM0 171l128 -2v-146l-128 25v123zM363 165v-186l-214 40v150z" />
+<glyph unicode="&#xf364;" horiz-adv-x="469" d="M384 192v-107h-107v43h64v64h43zM128 256v-64h-43v107h107v-43h-64zM427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384v-300h384z" />
+<glyph unicode="&#xf365;" d="M299 235v-43h-43v43h43zM299 149v-42h-43v42h43zM128 235v-43h-43v43h43zM213 235v-43h-42v43h42zM384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM384 64v256h-341v-256 h341z" />
+<glyph unicode="&#xf366;" d="M170.5 256q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM170.5 171q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM106.5 245q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM170.5 96q10.5 0 10.5 -10.5 t-10.5 -10.5t-10.5 10.5t10.5 10.5zM106.5 160q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM170.5 288q-10.5 0 -10.5 10.5t10.5 10.5t10.5 -10.5t-10.5 -10.5zM256 256q9 0 15 -6.5t6 -15t-6 -15t-15 -6.5t-15 6.5t-6 15t6 15t15 6.5zM256 288q-11 0 -11 10.5 t11 10.5t11 -10.5t-11 -10.5zM320 160q11 0 11 -10.5t-11 -10.5t-11 10.5t11 10.5zM320 245q11 0 11 -10.5t-11 -10.5t-11 10.5t11 10.5zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21 q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM256 96q11 0 11 -10.5t-11 -10.5t-11 10.5t11 10.5zM256 171q9 0 15 -6.5t6 -15t-6 -15t-15 -6.5t-15 6.5t-6 15t6 15t15 6.5z" />
+<glyph unicode="&#xf367;" horiz-adv-x="384" d="M42.5 75q-13.5 0 -22.5 9t-9 22.5t9 23t22.5 9.5t23 -9.5t9.5 -23t-9.5 -22.5t-23 -9zM128 171q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15t-15 -6zM128 256q-9 0 -15 6.5t-6 15t6 15t15 6.5t15 -6.5t6 -15t-6 -15t-15 -6.5zM0 0v43h384v-43h-384zM42.5 245 q-13.5 0 -22.5 9.5t-9 23t9 22.5t22.5 9t23 -9t9.5 -22.5t-9.5 -23t-23 -9.5zM42.5 160q-13.5 0 -22.5 9.5t-9 22.5t9 22.5t22.5 9.5t23 -9.5t9.5 -22.5t-9.5 -22.5t-23 -9.5zM128 85q-9 0 -15 6.5t-6 15t6 15t15 6.5t15 -6.5t6 -15t-6 -15t-15 -6.5zM298.5 96 q-10.5 0 -10.5 10.5t10.5 10.5t10.5 -10.5t-10.5 -10.5zM0 384h384v-43h-384v43zM298.5 267q-10.5 0 -10.5 10.5t10.5 10.5t10.5 -10.5t-10.5 -10.5zM298.5 181q-10.5 0 -10.5 11t10.5 11t10.5 -11t-10.5 -11zM213.5 256q-8.5 0 -15 6.5t-6.5 15t6.5 15t15 6.5t15 -6.5 t6.5 -15t-6.5 -15t-15 -6.5zM213.5 171q-8.5 0 -15 6t-6.5 15t6.5 15t15 6t15 -6t6.5 -15t-6.5 -15t-15 -6zM213.5 85q-8.5 0 -15 6.5t-6.5 15t6.5 15t15 6.5t15 -6.5t6.5 -15t-6.5 -15t-15 -6.5z" />
+<glyph unicode="&#xf368;" horiz-adv-x="405" d="M245.5 299q-8.5 0 -15 6t-6.5 15t6.5 15t15 6t15 -6t6.5 -15t-6.5 -15t-15 -6zM241 203q-11 2 -18.5 9.5t-8.5 17.5l-1 5q0 13 9.5 22.5t23 9.5t22.5 -9.5t9 -23t-9 -22.5t-23 -9h-4zM245.5 373q-10.5 0 -10.5 11t10.5 11t10.5 -11t-10.5 -11zM160 373q-11 0 -11 11 t11 11t11 -11t-11 -11zM394.5 224q-10.5 0 -10.5 10.5t10.5 10.5t10.5 -10.5t-10.5 -10.5zM160 299q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15t-15 -6zM330.5 128q-8.5 0 -15 6.5t-6.5 15t6.5 15t15 6.5t15 -6.5t6.5 -15t-6.5 -15t-15 -6.5zM330.5 213q-8.5 0 -15 6.5 t-6.5 15t6.5 15t15 6.5t15 -6.5t6.5 -15t-6.5 -15t-15 -6.5zM330.5 299q-8.5 0 -15 6t-6.5 15t6.5 15t15 6t15 -6t6.5 -15t-6.5 -15t-15 -6zM245.5 11q10.5 0 10.5 -11t-10.5 -11t-10.5 11t10.5 11zM0 336l27 27l346 -347l-27 -27l-81 81q2 -4 2 -6q0 -9 -6.5 -15t-15 -6 t-15 6t-6.5 15t6.5 15t14.5 6q2 0 6 -1l-60 60q-1 -11 -10 -19t-21 -8q-13 0 -22.5 9.5t-9.5 22.5q0 12 7.5 21t19.5 11l-60 60q1 -4 1 -6q0 -9 -6.5 -15.5t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15.5 6.5l6 -1zM160 85q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6z M394.5 160q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM74.5 171q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM10.5 245q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM160 11q11 0 11 -11t-11 -11t-11 11t11 11zM74.5 85 q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6zM10.5 160q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5z" />
+<glyph unicode="&#xf369;" horiz-adv-x="405" d="M74.5 171q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM74.5 85q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6zM74.5 256q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM10.5 245 q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM74.5 341q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6zM394.5 224q-10.5 0 -10.5 10.5t10.5 10.5t10.5 -10.5t-10.5 -10.5zM245.5 299q-8.5 0 -15 6t-6.5 15t6.5 15t15 6t15 -6t6.5 -15t-6.5 -15 t-15 -6zM245.5 373q-10.5 0 -10.5 11t10.5 11t10.5 -11t-10.5 -11zM10.5 160q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM160 11q11 0 11 -11t-11 -11t-11 11t11 11zM160 373q-11 0 -11 11t11 11t11 -11t-11 -11zM160 299q-9 0 -15 6t-6 15t6 15t15 6t15 -6 t6 -15t-6 -15t-15 -6zM160 181q13 0 22.5 -9t9.5 -22.5t-9.5 -23t-22.5 -9.5t-22.5 9.5t-9.5 23t9.5 22.5t22.5 9zM330.5 171q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM330.5 85q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15 t15 6zM330.5 256q8.5 0 15 -6.5t6.5 -15t-6.5 -15t-15 -6.5t-15 6.5t-6.5 15t6.5 15t15 6.5zM330.5 341q8.5 0 15 -6t6.5 -15t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6zM394.5 160q10.5 0 10.5 -10.5t-10.5 -10.5t-10.5 10.5t10.5 10.5zM245.5 85q8.5 0 15 -6t6.5 -15 t-6.5 -15t-15 -6t-15 6t-6.5 15t6.5 15t15 6zM245.5 11q10.5 0 10.5 -11t-10.5 -11t-10.5 11t10.5 11zM160 267q13 0 22.5 -9.5t9.5 -23t-9.5 -22.5t-22.5 -9t-22.5 9t-9.5 22.5t9.5 23t22.5 9.5zM160 85q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM245.5 181 q13.5 0 22.5 -9t9 -22.5t-9 -23t-22.5 -9.5t-23 9.5t-9.5 23t9.5 22.5t23 9zM245.5 267q13.5 0 22.5 -9.5t9 -23t-9 -22.5t-22.5 -9t-23 9t-9.5 22.5t9.5 23t23 9.5z" />
+<glyph unicode="&#xf36a;" horiz-adv-x="320" d="M107 405q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5q-57 0 -107 28q49 29 78 78t29 107t-29 107t-78 78q50 28 107 28z" />
+<glyph unicode="&#xf36b;" horiz-adv-x="277" d="M64 405q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5q-33 0 -64 9q66 21 107.5 77t41.5 127t-41.5 127t-107.5 77q31 9 64 9z" />
+<glyph unicode="&#xf36c;" horiz-adv-x="483" d="M412 263l71 -71l-71 -71v-100h-100l-71 -70l-70 70h-100v100l-71 71l71 71v100h100l70 70l71 -70h100v-100zM241 64q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5q-28 0 -53 -12q33 -15 54 -46.5t21 -69.5t-21 -69.5t-54 -46.5q25 -12 53 -12z" />
+<glyph unicode="&#xf36d;" horiz-adv-x="483" d="M412 121v-100h-100l-71 -70l-70 70h-100v100l-71 71l71 71v100h100l70 70l71 -70h100v-100l71 -71zM241 64q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5z" />
+<glyph unicode="&#xf36e;" horiz-adv-x="483" d="M412 121v-100h-100l-71 -70l-70 70h-100v100l-71 71l71 71v100h100l70 70l71 -70h100v-100l71 -71zM241 64q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-256z" />
+<glyph unicode="&#xf36f;" horiz-adv-x="483" d="M412 263l71 -71l-71 -71v-100h-100l-71 -70l-70 70h-100v100l-71 71l71 71v100h100l70 70l71 -70h100v-100zM241 64q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM241.5 277q35.5 0 60.5 -25t25 -60t-25 -60 t-60.5 -25t-60.5 25t-25 60t25 60t60.5 25z" />
+<glyph unicode="&#xf370;" horiz-adv-x="483" d="M217 178l24 78l25 -78h-49zM412 263l71 -71l-71 -71v-100h-100l-71 -70l-70 70h-100v100l-71 71l71 71v100h100l70 70l71 -70h100v-100zM290 107h41l-68 192h-43l-68 -192h40l15 42h68z" />
+<glyph unicode="&#xf371;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384v-300h384zM149 107v53l-32 32l32 32v53h54l32 32l32 -32h53v-53l32 -32l-32 -32v-53h-53l-32 -32l-32 32 h-54zM235 256v-128q26 0 45 18.5t19 45.5t-19 45.5t-45 18.5z" />
+<glyph unicode="&#xf372;" horiz-adv-x="384" d="M384 341v-140l-64 64l-85 -86l-86 86l-85 -86l-64 65v97q0 18 12.5 30.5t30.5 12.5h298q18 0 30.5 -12.5t12.5 -30.5zM320 204l64 -64v-97q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v140l64 -64l85 86l86 -86z" />
+<glyph unicode="&#xf373;" horiz-adv-x="384" d="M192 277q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM43 128v-85h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v85h43zM43 341v-85h-43v85q0 18 12.5 30.5t30.5 12.5h85v-43h-85zM341 384q18 0 30.5 -12.5t12.5 -30.5v-85h-43v85h-85v43h85zM341 43v85 h43v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-85v43h85z" />
+<glyph unicode="&#xf374;" horiz-adv-x="384" d="M43 128v-85h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v85h43zM43 341v-85h-43v85q0 18 12.5 30.5t30.5 12.5h85v-43h-85zM341 384q18 0 30.5 -12.5t12.5 -30.5v-85h-43v85h-85v43h85zM341 43v85h43v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-85v43h85zM192 277q35 0 60 -25 t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM192 149q18 0 30.5 12.5t12.5 30.5t-12.5 30.5t-30.5 12.5t-30.5 -12.5t-12.5 -30.5t12.5 -30.5t30.5 -12.5z" />
+<glyph unicode="&#xf375;" horiz-adv-x="384" d="M149 384v43h43v-470h-43v43h-106q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h106zM149 64v128l-106 -128h106zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-106v192l106 -128v277h-106v43h106z" />
+<glyph unicode="&#xf376;" horiz-adv-x="384" d="M341 320q18 0 30.5 -12.5t12.5 -30.5v-170q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v170q0 18 12.5 30.5t30.5 12.5h298zM341 107v170h-298v-170h298z" />
+<glyph unicode="&#xf377;" horiz-adv-x="384" d="M341 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h298zM341 64v256h-298v-256h298z" />
+<glyph unicode="&#xf378;" horiz-adv-x="384" d="M341 341q18 0 30.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h298zM341 85v214h-298v-214h298z" />
+<glyph unicode="&#xf379;" horiz-adv-x="384" d="M341 299q18 0 30.5 -12.5t12.5 -30.5v-128q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v128q0 18 12.5 30.5t30.5 12.5h298zM341 128v128h-298v-128h298z" />
+<glyph unicode="&#xf37a;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM341 43v298h-298v-298h298z" />
+<glyph unicode="&#xf37b;" horiz-adv-x="384" d="M0 341q0 18 12.5 30.5t30.5 12.5h85v-43h-85v-85h-43v85zM43 128v-85h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v85h43zM341 43v85h43v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-85v43h85zM341 384q18 0 30.5 -12.5t12.5 -30.5v-85h-43v85h-85v43h85z" />
+<glyph unicode="&#xf37c;" horiz-adv-x="384" d="M341 341q18 0 30.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t30.5 12.5h298zM341 85v214h-298v-214h298z" />
+<glyph unicode="&#xf37d;" horiz-adv-x="299" d="M256 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h213zM256 43v298h-213v-298h213z" />
+<glyph unicode="&#xf37e;" horiz-adv-x="341" d="M299 363q17 0 29.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-29.5 -12.5h-256q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h256zM299 64v256h-256v-256h256z" />
+<glyph unicode="&#xf37f;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM53 288v-32h43v-43h32v43h43v32h-43v43h-32v-43h-43zM341 43v298l-298 -298h298zM299 85h-107v32h107v-32z" />
+<glyph unicode="&#xf380;" d="M277 85h-42v43h42v43h43v-43h43v-43h-43v-42h-43v42zM384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341zM64 341v-42h128v42h-128zM384 21v342l-341 -342h341z" />
+<glyph unicode="&#xf381;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM341 43v298h-149v-128l-149 -170h149v170z" />
+<glyph unicode="&#xf382;" horiz-adv-x="384" d="M43 128v-85h85v-43h-85q-18 0 -30.5 12.5t-12.5 30.5v85h43zM43 341v-85h-43v85q0 18 12.5 30.5t30.5 12.5h85v-43h-85zM341 384q18 0 30.5 -12.5t12.5 -30.5v-85h-43v85h-85v43h85zM341 43v85h43v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-85v43h85zM192 256q27 0 45.5 -18.5 t18.5 -45.5t-18.5 -45.5t-45.5 -18.5t-45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5z" />
+<glyph unicode="&#xf383;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-299q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v299q0 18 12.5 30.5t30.5 12.5h85l85 85l86 -85h85zM384 21v299h-96l-74 75l-75 -75h-96v-299h341zM341 277h-256v-213h256v213z" />
+<glyph unicode="&#xf384;" horiz-adv-x="425" d="M191 361q-46 -5 -83 -34l-31 30q50 41 114 47v-43zM347 357l-30 -30q-38 29 -83 34v43q63 -6 113 -47zM381 213q-5 46 -34 84l30 30q41 -50 48 -114h-44zM78 297q-29 -38 -35 -84h-43q6 64 47 114zM43 171q6 -46 35 -83l-31 -31q-41 50 -47 114h43zM276 192 q0 -27 -18.5 -45.5t-45 -18.5t-45.5 18.5t-19 45.5t19 45.5t45.5 18.5t45 -18.5t18.5 -45.5zM347 87q29 38 34 83h44q-7 -63 -48 -113zM234 23q46 6 83 34l30 -30q-50 -41 -113 -47v43zM77 27l31 30q37 -29 83 -34v-43q-64 6 -114 47z" />
+<glyph unicode="&#xf385;" horiz-adv-x="384" d="M171 256h42v-43h-42v43zM128 213h43v-42h-43v42zM213 213h43v-42h-43v42zM256 256h43v-43h-43v43zM85 256h43v-43h-43v43zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298z M128 64v43h-43v-43h43zM213 64v43h-42v-43h42zM299 64v43h-43v-43h43zM341 213v128h-298v-128h42v-42h-42v-43h42v43h43v-43h43v43h42v-43h43v43h43v-43h42v43h-42v42h42z" />
+<glyph unicode="&#xf386;" horiz-adv-x="341" d="M128 192q18 0 30.5 -12.5t12.5 -30t-12.5 -30t-30.5 -12.5t-30.5 12.5t-12.5 30t12.5 30t30.5 12.5zM42.5 277q17.5 0 30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5zM42.5 107q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5 t-12.5 30.5t12.5 30.5t30 12.5zM298.5 277q-17.5 0 -30 12.5t-12.5 30.5t12.5 30.5t30 12.5t30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5zM213.5 107q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5zM298.5 192q17.5 0 30 -12.5 t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5zM213.5 277q17.5 0 30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5t-12.5 30t12.5 30t30 12.5zM128 363q18 0 30.5 -12.5t12.5 -30.5t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5t12.5 30.5 t30.5 12.5z" />
+<glyph unicode="&#xf387;" horiz-adv-x="384" d="M85 64v256h43v-256h-43zM171 -21v426h42v-426h-42zM0 149v86h43v-86h-43zM256 64v256h43v-256h-43zM341 235h43v-86h-43v86z" />
+<glyph unicode="&#xf388;" horiz-adv-x="431" d="M343 128h-8l-24 23v105h75q13 0 22.5 -9.5t9.5 -22.5v-21q0 -10 -5.5 -18.5t-13.5 -11.5l19 -45h-32l-19 43h-24v-43zM343 224v-21h43v21h-43zM247 224h-8l-32 32h40q13 0 22.5 -9.5t9.5 -22.5v-41l-32 32v9zM173 245l258 -256l-24 -23l-162 162h-72v73l-32 32v-105h-32 v53h-43v-53h-32v128h32v-43h43v43h8l-117 117l23 22z" />
+<glyph unicode="&#xf389;" horiz-adv-x="469" d="M341 320q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM85.5 277q35.5 0 60.5 -25t25 -60t-25 -60t-60.5 -25t-60.5 25t-25 60t25 60t60.5 25zM85.5 149q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5 t-12.5 -30.5t12.5 -30.5t30 -12.5z" />
+<glyph unicode="&#xf38a;" horiz-adv-x="469" d="M85.5 277q35.5 0 60.5 -25t25 -60t-25 -60t-60.5 -25t-60.5 25t-25 60t25 60t60.5 25zM341 320q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM341.5 107q35.5 0 60.5 25t25 60t-25 60t-60.5 25t-60.5 -25t-25 -60 t25 -60t60.5 -25z" />
+<glyph unicode="&#xf38b;" horiz-adv-x="384" d="M384 203q0 -19 -19 -30l19 -45h-32l-19 43h-24v-43h-32v128h75q13 0 22.5 -9.5t9.5 -22.5v-21zM352 203v21h-43v-21h43zM75 213v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43zM213 256q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-74v128h74zM213 160 v64h-42v-64h42z" />
+<glyph unicode="&#xf38c;" horiz-adv-x="361" d="M31 139v128h299v-128h-299zM159 436h43v-63h-43v63zM330 383l31 -30l-39 -38l-30 30zM202 -31h-43v63h43v-63zM361 52l-31 -30l-38 39l30 30zM0 353l30 30l38 -38l-30 -30zM30 22l-30 31l38 38l30 -30z" />
+<glyph unicode="&#xf38d;" horiz-adv-x="384" d="M149 384q0 -30 -11 -57l-34 34q3 11 3 23h42zM0 357l27 27l357 -357l-27 -27l-61 61q-19 -28 -19 -61h-42q0 51 31 91l-31 30q-43 -52 -43 -121h-43q0 86 56 152l-53 53q-66 -56 -152 -56v43q68 0 122 43l-31 31q-40 -31 -91 -31v42q33 0 61 19zM235 384q0 -64 -34 -120 l-31 31q22 42 22 89h43zM361 104l-34 34q28 11 57 11v-42q-12 0 -23 -3zM264 201q56 34 120 34v-43q-47 0 -89 -22z" />
+<glyph unicode="&#xf38e;" horiz-adv-x="384" d="M64 384q0 -27 -18.5 -45.5t-45.5 -18.5v64h64zM235 384q0 -97 -69 -166t-166 -69v43q80 0 136 56t56 136h43zM149 384q0 -62 -43.5 -105.5t-105.5 -43.5v42q44 0 75.5 31.5t31.5 75.5h42zM149 0q0 97 69 166t166 69v-43q-80 0 -136 -56t-56 -136h-43zM320 0 q0 27 18.5 45.5t45.5 18.5v-64h-64zM235 0q0 62 43.5 105.5t105.5 43.5v-42q-44 0 -75.5 -31.5t-31.5 -75.5h-42z" />
+<glyph unicode="&#xf38f;" horiz-adv-x="469" d="M234.5 235q61.5 0 105.5 -44t44 -106h-43q0 44 -31 75.5t-75 31.5t-75.5 -31.5t-31.5 -75.5h-43q0 62 44 106t105.5 44zM235 320q97 0 165.5 -69t68.5 -166h-42q0 80 -56.5 136t-136 56t-135.5 -56t-56 -136h-43q0 97 69 166t166 69z" />
+<glyph unicode="&#xf390;" d="M235 299v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43zM213 405q88 0 151 -62.5t63 -150.5v-171q0 -17 -12.5 -29.5t-30.5 -12.5h-171q-88 0 -150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121 t50 -121t120.5 -50z" />
+<glyph unicode="&#xf391;" d="M384 308q-84 -25 -171 -24q-87 0 -170 24v-232q83 24 170 24t171 -24v232zM415 363q12 0 12 -14v-314q0 -14 -12 -14q-4 0 -7 2q-94 35 -195 35t-194 -35q-4 -2 -7 -2q-12 0 -12 14v314q0 14 12 14q3 0 7 -2q94 -35 194 -35q101 0 195 35q3 2 7 2z" />
+<glyph unicode="&#xf392;" horiz-adv-x="342" d="M340 -3l2 -6q0 -12 -14 -12h-315q-13 0 -13 12q0 3 1 6q35 95 35 195t-35 195q-1 3 -1 6q0 12 13 12h315q13 0 13 -12q0 -3 -1 -6q-35 -95 -35 -195q0 -101 35 -195zM54 21h233q-25 84 -25 171t25 171h-233q25 -84 25 -171t-25 -171z" />
+<glyph unicode="&#xf393;" d="M213.5 320q-75.5 0 -155.5 -14q-15 -57 -15 -114t15 -114q80 -14 155.5 -14t155.5 14q15 57 15 114t-15 114q-80 14 -155.5 14zM213 363q83 0 170 -16l20 -3l5 -19q19 -67 19 -133t-19 -133l-5 -19l-20 -3q-87 -16 -170 -16t-169 16l-20 3l-5 19q-19 67 -19 133t19 133 l5 19l20 3q87 16 169 16z" />
+<glyph unicode="&#xf394;" horiz-adv-x="469" d="M427 128h42v-43h-42v43zM427 213h42v-42h-42v42zM469 43q0 -16 -13 -29.5t-29 -13.5v43h42zM256 384h43v-43h-43v43zM427 299h42v-43h-42v43zM427 384q16 0 29 -13.5t13 -29.5h-42v43zM0 299h43v-43h-43v43zM341 384h43v-43h-43v43zM341 43h43v-43h-43v43zM43 384v-43 h-43q0 16 13.5 29.5t29.5 13.5zM171 384h42v-43h-42v43zM85 384h43v-43h-43v43zM0 213h299v-213h-256q-18 0 -30.5 12.5t-12.5 30.5v170zM43 43h213l-68 91l-54 -69l-38 46z" />
+<glyph unicode="&#xf395;" horiz-adv-x="469" d="M469 128v-43h-42v43h42zM469 213v-42h-42v42h42zM469 43q0 -16 -13 -29.5t-29 -13.5v43h42zM299 384v-43h-43v43h43zM469 299v-43h-42v43h42zM427 384q16 0 29 -13.5t13 -29.5h-42v43zM43 0q-18 0 -30.5 12.5t-12.5 30.5v85h213v-128h-170zM43 299v-43h-43v43h43zM299 43 v-43h-43v43h43zM384 384v-43h-43v43h43zM384 43v-43h-43v43h43zM43 384v-43h-43q0 16 13.5 29.5t29.5 13.5zM43 213v-42h-43v42h43zM213 384v-43h-42v43h42zM128 384v-43h-43v43h43z" />
+<glyph unicode="&#xf396;" horiz-adv-x="469" d="M384 299v-128h-171v128h171zM427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384v-300h384z" />
+<glyph unicode="&#xf397;" horiz-adv-x="384" d="M149 277l107 -85l-107 -85v170zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM341 43v298h-298v-298h298z" />
+<glyph unicode="&#xf398;" horiz-adv-x="384" d="M352 382q24 -6 31 -30l-351 -350q-11 3 -19 11t-11 19zM189 384h61l-250 -250v61zM43 384h42l-85 -85v42q0 18 12.5 30.5t30.5 12.5zM341 0h-42l85 85v-42q0 -18 -13 -30q-12 -13 -30 -13zM134 0l250 250v-61l-189 -189h-61z" />
+<glyph unicode="&#xf399;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM192 23v338q-64 -8 -106.5 -56t-42.5 -113t43 -113t106 -56zM235 361v-20h61q-29 16 -61 20zM235 299v-22h126q-7 12 -15 22h-111zM235 235v-22h148 q-2 9 -5 22h-143zM235 23q32 4 61 20h-61v-20zM346 85q8 10 15 22h-126v-22h111zM378 149q3 13 5 22h-148v-22h143z" />
+<glyph unicode="&#xf39a;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM234.5 64q70.5 0 120.5 37.5t50 90.5t-50 90.5t-120.5 37.5t-120.5 -37.5t-50 -90.5t50 -90.5t120.5 -37.5z" />
+<glyph unicode="&#xf39b;" horiz-adv-x="508" d="M146 178l25 78l24 -78h-49zM469 299h39l-44 -192h-37l-32 130l-32 -130h-38l-2 9q-21 -43 -62 -69t-90 -26q-71 0 -121 50t-50 121t50 121t121 50q81 0 133 -64h16l26 -135l32 135h34l32 -135zM220 107h40l-68 192h-43l-68 -192h41l15 42h68z" />
+<glyph unicode="&#xf39c;" horiz-adv-x="256" d="M128 269l-98 -98l-30 30l128 128l128 -128l-30 -30zM0 64v43h256v-43h-256z" />
+<glyph unicode="&#xf39d;" horiz-adv-x="299" d="M0 85h299v-42h-299v42zM149 299l143 -171h-285z" />
+<glyph unicode="&#xf39e;" horiz-adv-x="341" d="M128 21v342h85v-342h-85zM0 21v171h85v-171h-85zM256 256h85v-235h-85v235z" />
+<glyph unicode="&#xf39f;" horiz-adv-x="373" d="M0 64v256l181 -128zM192 320l181 -128l-181 -128v256z" />
+<glyph unicode="&#xf3a0;" horiz-adv-x="373" d="M181 64l-181 128l181 128v-256zM192 192l181 128v-256z" />
+<glyph unicode="&#xf3a1;" horiz-adv-x="341" d="M0 170.5q0 70.5 50 120.5t121 50v86l106 -107l-106 -107v86q-53 0 -90.5 -38t-37.5 -90.5t37.5 -90t90 -37.5t90.5 37.5t38 90.5h42q0 -71 -50 -121t-120.5 -50t-120.5 50t-50 120.5zM145 107h-17v70l-21 -6v15l38 12h2v-91h-2zM239 145q0 -13 -2 -17l-7 -13 q-6 -6 -10 -6q-2 0 -6.5 -1t-6.5 -1q-9 0 -13 2q-2 1 -5 3t-6 3q-2 1 -6 13q-2 6 -2 17v15q0 13 2 17l6 13q7 6 11 6q2 0 6.5 1t6.5 1q8 0 13 -2q2 -1 5 -3t5 -3q3 -1 7 -13q2 -6 2 -17v-15zM222 162v11q-2 4 -2 6l-5 4q-2 3 -6 3t-6 -3l-5 -4q-2 -4 -2 -6v-43q2 -4 2 -6 t2 -3t3 -2q2 -2 6 -2t6 2l5 5q2 4 2 6v32z" />
+<glyph unicode="&#xf3a2;" horiz-adv-x="341" d="M119 160h9q6 0 10.5 4.5t4.5 8.5v4q-2 2 -2 4t-4 2h-11q-2 -2 -4.5 -2t-2.5 -4v-4h-21q0 6 2 10.5t6.5 8.5t8.5 4q1 0 5.5 1t5.5 1q8 0 13 -2q2 -1 5 -2t5 -2q3 -1 7 -9q2 -4 2 -10v-7q-2 -4 -2 -6q0 -4 -5 -4q-2 0 -6 -5q9 -4 11 -8q4 -9 4 -13q0 -8 -2 -11q-1 -1 -3 -4 t-4 -4q-4 -4 -10 -4q-2 0 -6.5 -1t-6.5 -1q-9 0 -11 2q-1 1 -5 2t-5 2q-3 1 -7 8q-2 5 -2 13h17v-4q2 -2 2 -4t5 -2h10q2 2 4.5 2t2.5 4v11q-2 2 -2 4t-5 2h-13v15zM241 145q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-8 0 -13 2q-2 1 -5 3t-5 3q-3 1 -7 13 q-2 6 -2 17v15q0 13 2 17l7 13q6 6 10 6q2 0 6.5 1t6.5 1q9 0 13 -2q2 -1 5 -3t6 -3q2 -1 6 -13q2 -6 2 -17v-15zM222 162v11q-2 4 -2 6l-5 4q-2 3 -6 3t-6 -3l-5 -4q-2 -4 -2 -6v-43q2 -4 2 -6l5 -5q2 -2 6 -2t6 2l5 5q2 4 2 6v32zM0 170.5q0 70.5 50 120.5t121 50v86 l106 -107l-106 -107v86q-53 0 -90.5 -38t-37.5 -90.5t37.5 -90t90 -37.5t90.5 37.5t38 90.5h42q0 -71 -50 -121t-120.5 -50t-120.5 50t-50 120.5z" />
+<glyph unicode="&#xf3a3;" horiz-adv-x="341" d="M0 170.5q0 70.5 50 120.5t121 50v86l106 -107l-106 -107v86q-53 0 -90.5 -38t-37.5 -90.5t37.5 -90t90 -37.5t90.5 37.5t38 90.5h42q0 -71 -50 -121t-120.5 -50t-120.5 50t-50 120.5zM143 151l4 47h51v-15h-36l-2 -19q2 0 2 2q0 1 1 1t1 2h5h4q8 0 10 -3q2 -1 5 -3t4 -3 q2 -2 6 -11q3 -4 3 -12.5t-3 -10.5q0 -1 -2 -4.5t-4 -6.5q-2 -2 -11 -6q-4 -2 -12.5 -2t-10.5 2q-1 1 -5 2t-6 2q-3 1 -6 9q-2 4 -2 10h17q0 -4 4 -8q2 -2 9 -2q4 0 6 2l4 4q2 4 2 6v13l-2 4l-4 5q-4 2 -6 2h-5q-2 0 -4 -2q-1 -1 -1.5 -1t-0.5 -1l-2 -3h-13z" />
+<glyph unicode="&#xf3a4;" horiz-adv-x="384" d="M299 21q17 0 29.5 12.5t12.5 30.5h43q0 -35 -25 -60t-60 -25q-19 0 -35 7q-41 21 -59 76q-4 14 -12 22.5t-24 21.5q-41 31 -61 67q-23 41 -23 83q0 63 43.5 106t106.5 43t106 -43t43 -106h-43q0 45 -31 76t-75.5 31t-75.5 -31t-31 -76q0 -31 17 -63q16 -27 50 -54 q13 -10 20 -16t16.5 -19t14.5 -29q13 -38 36 -50q8 -4 17 -4zM99 392q-56 -56 -56 -136q0 -79 56 -136l-30 -30q-69 69 -69 166t69 166zM181 256q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5z" />
+<glyph unicode="&#xf3a5;" d="M149 107v170h43v-170h-43zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50zM235 107v170h42v-170h-42z" />
+<glyph unicode="&#xf3a6;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM192 107v170h-43v-170h43zM277 107v170h-42v-170h42z" />
+<glyph unicode="&#xf3a7;" horiz-adv-x="256" d="M0 43v298h85v-298h-85zM171 341h85v-298h-85v298z" />
+<glyph unicode="&#xf3a8;" d="M171 96v192l128 -96zM213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM213.5 21q70.5 0 120.5 50t50 121t-50 121t-120.5 50t-120.5 -50t-50 -121t50 -121t120.5 -50z" />
+<glyph unicode="&#xf3a9;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM171 96l128 96l-128 96v-192z" />
+<glyph unicode="&#xf3aa;" horiz-adv-x="235" d="M0 341l235 -149l-235 -149v298z" />
+<glyph unicode="&#xf3ab;" horiz-adv-x="405" d="M256 320v-43h-256v43h256zM256 235v-43h-256v43h256zM0 107v42h171v-42h-171zM299 320h106v-43h-64v-192q0 -26 -18.5 -45t-45 -19t-45.5 19t-19 45.5t19 45t45 18.5q11 0 22 -4v175z" />
+<glyph unicode="&#xf3ac;" d="M256 235v-43h-256v43h256zM256 320v-43h-256v43h256zM341 149h86v-42h-86v-86h-42v86h-86v42h86v86h42v-86zM0 107v42h171v-42h-171z" />
+<glyph unicode="&#xf3ad;" horiz-adv-x="384" d="M85 299v-86h-42v128h256v64l85 -85l-85 -85v64h-214zM299 85v86h42v-128h-256v-64l-85 85l85 85v-64h214zM213 128h-32v85h-32v22l43 21h21v-128z" />
+<glyph unicode="&#xf3ae;" horiz-adv-x="384" d="M85 299v-86h-42v128h256v64l85 -85l-85 -85v64h-214zM299 85v86h42v-128h-256v-64l-85 85l85 85v-64h214z" />
+<glyph unicode="&#xf3af;" horiz-adv-x="341" d="M239 124q0 -20 -8 -30t-23 -10t-23 10t-8 29v17q0 19 8 29t23 10t23 -10t8 -28v-17zM221 142q0 12 -3 17t-10 5t-10 -5t-3 -15v-23q0 -11 3 -16.5t10 -5.5t10 5t3 16v22zM147 85h-19v71l-22 -7v15l39 14h2v-93zM171 320q70 0 120 -50t50 -120.5t-50 -120.5t-120.5 -50 t-120.5 50t-50 120h43q0 -52 37.5 -90t90 -38t90.5 38t38 90.5t-38 90t-90 37.5v-85l-107 107l107 106v-85z" />
+<glyph unicode="&#xf3b0;" horiz-adv-x="341" d="M239 123q0 -20 -8 -30t-23.5 -10t-23.5 10t-8 29v17q0 20 8 30t23.5 10t23.5 -10t8 -29v-17zM221 142q0 11 -3.5 16.5t-10 5.5t-9.5 -5t-3 -16v-23q0 -11 3 -16.5t10 -5.5t10 5t3 16v23zM120 139h10q7 0 10 3.5t3 9.5t-3 9t-9 3t-9.5 -3t-3.5 -8h-18q0 8 4 13.5t11 9 t15 3.5q15 0 23.5 -7t8.5 -20q0 -6 -4 -11.5t-10 -8.5q8 -3 11.5 -8.5t3.5 -13.5q0 -12 -9 -19.5t-24 -7.5q-14 0 -23 7t-9 20h19q0 -6 4 -9t10 -3t10 3.5t4 8.5q0 14 -16 14h-9v15zM171 320q70 0 120 -50t50 -120.5t-50 -120.5t-120.5 -50t-120.5 50t-50 120h43 q0 -52 37.5 -90t90 -38t90.5 38t38 90.5t-38 90t-90 37.5v-85l-107 107l107 106v-85z" />
+<glyph unicode="&#xf3b1;" horiz-adv-x="341" d="M142 131l5 46h51v-15h-36l-2 -20q6 4 13 4q13 0 20.5 -8t7.5 -23q0 -8 -4 -15t-10.5 -11t-16.5 -4q-8 0 -15 3.5t-11 9.5t-4 13h18q0 -5 3.5 -8t8.5 -3q6 0 9.5 4t3.5 12t-4 12t-11 4q-6 0 -10 -3l-2 -2zM171 320q70 0 120 -50t50 -120.5t-50 -120.5t-120.5 -50 t-120.5 50t-50 120h43q0 -52 37.5 -90t90 -38t90.5 38t38 90.5t-38 90t-90 37.5v-85l-107 107l107 106v-85z" />
+<glyph unicode="&#xf3b2;" horiz-adv-x="341" d="M171 341q70 0 120 -50t50 -120.5t-50 -120.5t-120.5 -50t-120.5 50t-50 121h43q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-86l-107 107l107 107v-86z" />
+<glyph unicode="&#xf3b3;" horiz-adv-x="341" d="M141 252l-31 -30l-110 111l30 30zM224 363h117v-118l-43 44l-268 -268l-30 30l268 268zM231 162l67 -67l43 44v-118h-117l44 44l-67 67z" />
+<glyph unicode="&#xf3b4;" horiz-adv-x="256" d="M0 64v256l181 -128zM213 320h43v-256h-43v256z" />
+<glyph unicode="&#xf3b5;" horiz-adv-x="256" d="M0 320h43v-256h-43v256zM75 192l181 128v-256z" />
+<glyph unicode="&#xf3b6;" horiz-adv-x="256" d="M0 320h256v-256h-256v256z" />
+<glyph unicode="&#xf3b7;" d="M384 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h341zM123 102q-38 37 -38 90t38 91l-30 30q-50 -50 -50 -121t50 -121zM213.5 107q35.5 0 60.5 25t25 60t-25 60t-60.5 25 t-60.5 -25t-25 -60t25 -60t60.5 -25zM334 71q50 50 50 121t-50 121l-30 -31q37 -37 37 -90t-37 -91zM213.5 235q17.5 0 30 -12.5t12.5 -30.5t-12.5 -30.5t-30 -12.5t-30 12.5t-12.5 30.5t12.5 30.5t30 12.5z" />
+<glyph unicode="&#xf3b8;" horiz-adv-x="384" d="M0 85h128v-42h-128v42zM0 341h213v-42h-213v42zM213 0h-42v128h42v-43h171v-42h-171v-43zM85 256h43v-128h-43v43h-85v42h85v43zM384 171h-213v42h213v-42zM256 256v128h43v-43h85v-42h-85v-43h-43z" />
+<glyph unicode="&#xf3b9;" horiz-adv-x="288" d="M288 192q0 -28 -14.5 -51t-38.5 -35v172q24 -12 38.5 -35t14.5 -51zM0 256h85l107 107v-342l-107 107h-85v128z" />
+<glyph unicode="&#xf3ba;" horiz-adv-x="192" d="M0 256h85l107 107v-342l-107 107h-85v128z" />
+<glyph unicode="&#xf3bb;" horiz-adv-x="384" d="M288 192q0 -6 -1 -13l-52 52v47q24 -12 38.5 -35t14.5 -51zM341 192q0 50 -30 89.5t-76 53.5v44q64 -15 106.5 -67t42.5 -120q0 -47 -22 -89l-32 33q11 27 11 56zM27 384l165 -165l192 -192l-27 -27l-44 44q-35 -29 -78 -39v44q25 8 48 25l-91 91v-144l-107 107h-85v128 h101l-101 101zM192 363v-90l-45 45z" />
+<glyph unicode="&#xf3bc;" horiz-adv-x="384" d="M0 256h85l107 107v-342l-107 107h-85v128zM288 192q0 -28 -14.5 -51t-38.5 -35v172q24 -12 38.5 -35t14.5 -51zM235 379q64 -15 106.5 -67t42.5 -120t-42.5 -120t-106.5 -67v44q46 14 76 53.5t30 89.5t-30 89.5t-76 53.5v44z" />
+<glyph unicode="&#xf3bd;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM235 85v214h-86v-43h43v-171h43z" />
+<glyph unicode="&#xf3be;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 213v43q0 18 -12.5 30.5t-30.5 12.5h-85v-43h85v-43h-42q-18 0 -30.5 -12.5t-12.5 -29.5v-86h128v43h-85v43h42 q18 0 30.5 12.5t12.5 29.5z" />
+<glyph unicode="&#xf3bf;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 224v32q0 18 -12.5 30.5t-30.5 12.5h-85v-43h85v-43h-42v-42h42v-43h-85v-43h85q18 0 30.5 12.5t12.5 30.5v32 q0 13 -9.5 22.5t-22.5 9.5q13 0 22.5 9.5t9.5 22.5z" />
+<glyph unicode="&#xf3c0;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 85v214h-43v-86h-42v86h-43v-128h85v-86h43z" />
+<glyph unicode="&#xf3c1;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 256v43h-128v-128h85v-43h-85v-43h85q18 0 30.5 12.5t12.5 30.5v43q0 17 -12.5 29.5t-30.5 12.5h-42v43h85z" />
+<glyph unicode="&#xf3c2;" horiz-adv-x="384" d="M171 128v43h42v-43h-42zM341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 256v43h-85q-18 0 -30.5 -12.5t-12.5 -30.5v-128q0 -18 12.5 -30.5t30.5 -12.5h42 q18 0 30.5 12.5t12.5 30.5v43q0 17 -12.5 29.5t-30.5 12.5h-42v43h85z" />
+<glyph unicode="&#xf3c3;" horiz-adv-x="320" d="M0 213h171v-42h-171v42zM320 64h-43v227l-64 -22v36l101 36h6v-277z" />
+<glyph unicode="&#xf3c4;" horiz-adv-x="405" d="M278 100h127v-36h-184v32l89 97q10 11 19 22q7 8 12 18q4 7 6 14q2 8 2 14q0 9 -3 18q-3 8 -8 13q-5 7 -12.5 10t-17.5 3q-12 0 -20 -4q-9 -4 -15 -10q-6 -8 -8 -16q-3 -9 -3 -19h-46q1 17 6 32q6 16 18 28t29 19q18 6 40 6q20 0 36 -5q17 -6 27 -15q11 -10 17 -24t6 -31 q0 -13 -4 -25q-5 -12 -12 -25q-8 -13 -17 -25q-13 -15 -23 -25zM0 213h171v-42h-171v42z" />
+<glyph unicode="&#xf3c5;" horiz-adv-x="341" d="M128 299v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43zM341 64h-42v227l-64 -22v36l100 36h6v-277z" />
+<glyph unicode="&#xf3c6;" d="M300 100h127v-36h-184v32l89 97q10 11 18 22q7 8 12 18q4 7 6 14q2 8 2 14q0 9 -3 18q-3 8 -8 13q-5 7 -12 10t-17 3q-12 0 -21 -4t-14 -10q-6 -8 -9 -16q-2 -9 -3 -19h-45q0 17 6 32q6 16 17.5 28t29.5 19q17 6 39 6q20 0 37 -5q16 -6 27 -15q10 -10 16 -24t6 -31 q0 -13 -4 -25t-12 -25q-7 -13 -17 -25q-13 -15 -22 -25zM128 299v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43z" />
+<glyph unicode="&#xf3c7;" horiz-adv-x="512" d="M0 283l101 37h6v-256h-43v205l-64 -22v36zM507 141q5 -8 5 -21t-5 -23q-6 -11 -15 -18q-10 -7 -24 -11q-13 -4 -30 -4q-20 0 -34.5 5t-24.5 14q-9 9 -14.5 20t-5.5 22h41q0 -8 3 -14q4 -6 9 -9q5 -4 12 -5q6 -2 14 -2q16 0 24.5 6t8.5 17q0 4 -1 8q-2 4 -6 7q-5 4 -12 6 q-8 3 -20 6q-16 3 -28 8t-20 11q-9 6 -14 15t-5 21t5 21q5 11 14 18.5t23 12.5q13 4 29 4q18 0 32 -5t23 -12q10 -8 15 -19t5 -23h-42q0 4 -2 10t-6 9q-5 4 -10 6q-7 3 -14.5 3t-13.5 -2t-10 -5q-3 -3 -6 -8q-1 -4 -1 -8.5t1.5 -8t5.5 -6.5t12 -5q8 -3 19 -5q15 -4 28 -8 q12 -5 22 -12q9 -6 13 -16zM295 298q11 -13 16 -34q6 -21 6 -51v-41q0 -30 -6 -51q-5 -21 -16 -34q-11 -14 -26 -19q-15 -6 -34 -6q-18 0 -34 6q-15 5 -26 19q-11 12 -17 34q-6 20 -6 51v41q0 29 6 51q6 21 16.5 34t26 18.5t34.5 5.5t34 -5.5t26 -18.5zM275 166v54 q0 18 -2 32q-3 13 -8 21t-13 11.5t-17 3.5q-10 0 -18 -3q-7 -4 -12.5 -12t-8.5 -21q-2 -13 -2 -33v-53q0 -20 2 -33q3 -13 9 -21q5 -9 12.5 -12.5t17.5 -3.5t17.5 3.5t12.5 12t7.5 22t2.5 32.5z" />
+<glyph unicode="&#xf3c8;" horiz-adv-x="361" d="M158 171q5 -7 8 -16q2 -9 2 -18q0 -18 -6.5 -32t-17.5 -23q-12 -10 -27 -15q-16 -5 -34 -5q-16 0 -31 4.5t-27 13.5t-18 23q-7 13 -7 31h42q0 -9 3 -16t8.5 -12t13.5 -7q7 -3 17 -3q20 0 31 10q11 11 11 31q0 10 -3 18t-10 13q-5 5 -14 8q-9 2 -20 2h-25v33h25q11 0 19 3 t13 8q6 5 8.5 12t2.5 16q0 18 -9 28q-10 10 -29 10q-9 0 -16 -2.5t-12 -6.5q-5 -5 -8 -11.5t-3 -14.5h-43q0 15 7 27q5 12 16 22t26 15q14 5 32 5t32.5 -4t25.5 -13.5t17 -22.5q6 -14 6 -32q0 -8 -2 -15t-8 -15q-4 -7 -12 -14q-6 -6 -17 -11q12 -4 20 -10t13 -14zM356 141 q4 -8 5 -21q0 -13 -5 -23q-6 -11 -16 -18q-9 -7 -23 -11t-31 -4q-19 0 -33.5 5t-24.5 14t-15 20t-5 22h40q0 -8 4 -14t9 -9q5 -4 12 -5q6 -2 13 -2q17 0 25.5 6t8.5 17q0 4 -2 8q-1 4 -5 7q-5 4 -13 6q-8 3 -20 6q-15 3 -27 8q-13 5 -21 11q-9 7 -14 15q-4 10 -4 21 q0 12 4 21q6 11 15 18.5t22 12.5q13 4 30 4q18 0 31 -5q14 -5 23 -12q10 -8 15 -19t5 -23h-41q0 4 -2 10q-3 6 -7 9q-4 4 -10 6q-6 3 -14 3t-13.5 -2t-9.5 -5t-6 -8q-2 -4 -2 -8.5t1.5 -8t6 -6.5t11.5 -5q9 -3 19 -5q15 -4 29 -8q12 -5 21 -12q9 -6 14 -16z" />
+<glyph unicode="&#xf3c9;" horiz-adv-x="177" d="M177 181q0 -32 -6.5 -54t-18 -36t-28 -20.5t-35.5 -6.5q-20 0 -37 6q-16 7 -27 21q-12 14 -19 36q-6 22 -6 54v44q0 32 6.5 54t18 36t28 20t36 6t36 -6t28 -20t18 -36t6.5 -54v-44zM132 232q0 19 -3 34q-3 14 -8 23q-6 8 -14 12t-18 4q-11 0 -19 -4t-13 -12 q-6 -9 -9 -22.5t-3 -34.5v-57q0 -20 3 -35q3 -13 9 -23q5 -9 13 -13t19 -4t19 4t13 13t8 23t3 35v57z" />
+<glyph unicode="&#xf3ca;" horiz-adv-x="448" d="M443 143l-15 -40l-264 95l45 121l182 -66q34 -12 49 -44t3 -66zM0 189l15 40l405 -146l-14 -40l-97 34v-34h-170v96zM124 230.5q-24 -11.5 -49 -3t-36.5 32.5t-3 49t32.5 36.5t49 3t36.5 -32.5t3 -49t-32.5 -36.5z" />
+<glyph unicode="&#xf3cb;" d="M427 213v-42h-278v128h192q36 0 61 -25t25 -61zM0 149h427v-42h-128v-43h-171v43h-128v42zM109.5 190q-18.5 -19 -45 -19.5t-45.5 18.5t-19 45t18.5 45t45 19.5t45.5 -18t19 -45t-18.5 -45.5z" />
+<glyph unicode="&#xf3cc;" horiz-adv-x="469" d="M128 171q-26 0 -45 18.5t-19 45t19 45.5t45 19t45 -19t19 -45.5t-19 -45t-45 -18.5zM384 299q35 0 60 -25t25 -61v-128h-469v214h43v-150h170v150h171z" />
+<glyph unicode="&#xf3cd;" horiz-adv-x="448" d="M43 192q0 -27 18.5 -45.5t45.5 -18.5h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM444 80q7 -12 2.5 -25t-17.5 -19l-79 -36l-73 149h-149q-27 0 -45.5 19t-18.5 45v171h128v-128h75q26 0 38 -24l73 -149l23 11q12 5 24.5 1.5t18.5 -15.5z" />
+<glyph unicode="&#xf3ce;" horiz-adv-x="405" d="M43 192q0 -27 18.5 -45.5t45.5 -18.5h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM373 64q14 0 23 -9.5t9 -22.5t-9 -22.5t-23 -9.5h-96v149h-149q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -12.5t12.5 -30.5v-149h32z" />
+<glyph unicode="&#xf3cf;" horiz-adv-x="363" d="M362 38q3 -15 -6.5 -26.5t-24.5 -11.5h-96v64l21 85h-128q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -12.5t12.5 -30.5l-42 -149h30q12 0 21.5 -7t11.5 -19zM43 192q0 -27 18.5 -45.5t45.5 -18.5h85v-43h-85q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192z" />
+<glyph unicode="&#xf3d0;" d="M71.5 328q-14.5 10 -17.5 27.5t7 32t27.5 17.5t32 -7.5t17.5 -27.5t-7 -31.5t-27.5 -17.5t-32 7zM299 43v-43h-151q-39 0 -69 25.5t-37 64.5l-42 209h43l42 -202q3 -24 21 -39t42 -15h151zM304 128l123 -96l-32 -32l-82 64h-146q-23 0 -40.5 14.5t-22.5 37.5l-29 126 q-3 20 8.5 36.5t31.5 19.5q10 2 21 -1q10 -3 16 -8l35 -27q47 -37 100 -27v-46q-48 -8 -110 26l22 -87h105z" />
+<glyph unicode="&#xf3d1;" horiz-adv-x="341" d="M76.5 332.5q-12.5 12.5 -12.5 30t12.5 30t30 12.5t30 -12.5t12.5 -30t-12.5 -30t-30 -12.5t-30 12.5zM43 107q0 -27 18.5 -45.5t45.5 -18.5h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM341 20l-30 -31l-75 75h-108q-27 0 -45.5 18.5t-18.5 45.5v123 q0 20 14 34t34 14h1q10 0 20 -5q9 -4 15 -11l30 -33q17 -19 45 -31.5t54 -11.5v-47q-29 0 -61 13.5t-56 33.5v-79h73z" />
+<glyph unicode="&#xf3d2;" horiz-adv-x="469" d="M107 -21l128 128l128 -128h-256zM427 384q17 0 29.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-86v42h86v256h-384v-256h85v-42h-85q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+<glyph unicode="&#xf3d3;" horiz-adv-x="384" d="M341 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h298zM171 213v22q0 8 -6.5 14.5t-15.5 6.5h-64q-8 0 -14.5 -6.5t-6.5 -14.5v-86q0 -8 6.5 -14.5t14.5 -6.5h64q9 0 15.5 6.5 t6.5 14.5v22h-32v-11h-43v64h43v-11h32zM320 213v22q0 8 -6.5 14.5t-14.5 6.5h-64q-9 0 -15.5 -6.5t-6.5 -14.5v-86q0 -8 6.5 -14.5t15.5 -6.5h64q8 0 14.5 6.5t6.5 14.5v22h-32v-11h-43v64h43v-11h32z" />
+<glyph unicode="&#xf3d4;" d="M427 235q-18 0 -30.5 -12.5t-12.5 -30.5t12.5 -30.5t30.5 -12.5v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 30.5v85q18 0 30.5 12.5t12.5 30.5t-12.5 30.5t-30.5 12.5v85q0 18 12.5 30.5t30.5 12.5h341q18 0 30.5 -12.5t12.5 -30.5v-85zM235 75v42 h-43v-42h43zM235 171v42h-43v-42h43zM235 267v42h-43v-42h43z" />
+<glyph unicode="&#xf3d5;" d="M427 256h-43v-43h43v-42h-43v-43h43v-43h-43v-42q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298q18 0 30.5 -12.5t12.5 -30.5v-42h43v-43zM341 43v298h-298v-298h298zM85 171h107v-86h-107v86zM213 213h86v-64h-86 v64zM85 299h107v-107h-107v107zM213 107h86v-128h-86v128z" />
+<glyph unicode="&#xf3d6;" d="M384 107v42h43v-42h-43zM384 299h43v-107h-43v107zM170.5 363q70.5 0 120.5 -50t50 -121t-50 -121t-120.5 -50t-120.5 50t-50 121t50 121t120.5 50zM170.5 149q17.5 0 30 12.5t12.5 30.5t-12.5 30.5t-30 12.5t-30 -12.5t-12.5 -30.5t12.5 -30.5t30 -12.5z" />
+<glyph unicode="&#xf3d7;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM256 256v43h-128v-214h128v43h-85v43h85v42h-85v43h85z" />
+<glyph unicode="&#xf3d8;" horiz-adv-x="405" d="M0 32h405v-43h-405v43zM153 154l-113 31l-34 9v110l31 -8l20 -50l106 -28v177l41 -11l59 -193l113 -30q13 -3 19.5 -14.5t3 -24.5t-15 -19.5t-24.5 -3.5l-113 30z" />
+<glyph unicode="&#xf3d9;" horiz-adv-x="433" d="M14 49h405v-43h-405v43zM431.5 248.5q3.5 -12.5 -3 -24t-19.5 -15.5l-114 -30l-92 -25l-114 -30l-34 -10l-16 29l-39 67l31 9l42 -33l106 28l-88 153l41 11l147 -137l113 30q13 4 24.5 -3t15 -19.5z" />
+<glyph unicode="&#xf3da;" horiz-adv-x="384" d="M128 299v-43h-43v43h43zM128 213v-42h-43v42h43zM128 384v-43h-43q0 18 12.5 30.5t30.5 12.5zM213 128v-43h-42v43h42zM341 384q18 0 30.5 -12.5t12.5 -30.5h-43v43zM213 384v-43h-42v43h42zM128 85q-18 0 -30.5 12.5t-12.5 30.5h43v-43zM341 171v42h43v-42h-43zM341 256 v43h43v-43h-43zM341 85v43h43q0 -18 -12.5 -30.5t-30.5 -12.5zM43 299v-256h256v-43h-256q-18 0 -30.5 12.5t-12.5 30.5v256h43zM256 341v43h43v-43h-43zM256 85v43h43v-43h-43z" />
+<glyph unicode="&#xf3db;" horiz-adv-x="384" d="M0 171v42h43v-42h-43zM0 85v43h43v-43h-43zM43 0q-18 0 -30.5 12.5t-12.5 30.5h43v-43zM0 256v43h43v-43h-43zM256 0v43h43v-43h-43zM341 384q18 0 30.5 -12.5t12.5 -30.5v-213q0 -18 -12.5 -30.5t-30.5 -12.5h-213q-18 0 -30.5 12.5t-12.5 30.5v213q0 18 12.5 30.5 t30.5 12.5h213zM341 128v213h-213v-213h213zM171 0v43h42v-43h-42zM85 0v43h43v-43h-43z" />
+<glyph unicode="&#xf3dc;" d="M213.5 405q88.5 0 151 -62.5t62.5 -150.5t-62.5 -150.5t-151 -62.5t-151 62.5t-62.5 150.5t62.5 150.5t151 62.5zM128 75q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM160 277q0 -22 15.5 -37.5t37.5 -15.5t38 15.5 t16 37.5t-16 38t-38 16t-37.5 -16t-15.5 -38zM299 75q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-38 -15.5t-16 -37.5t16 -37.5t38 -15.5z" />
+<glyph unicode="&#xf3dd;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM171 128v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM213 256v-128h86q8 0 14.5 6.5t6.5 14.5v86q0 8 -6.5 14.5 t-14.5 6.5h-86zM245 160v64h43v-64h-43z" />
+<glyph unicode="&#xf3de;" horiz-adv-x="384" d="M341 363q18 0 30.5 -12.5t12.5 -30.5v-256q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v256q0 18 12.5 30.5t30.5 12.5h298zM171 128v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM320 149v86q0 8 -6.5 14.5t-14.5 6.5h-64q-9 0 -15.5 -6.5 t-6.5 -14.5v-86q0 -8 6.5 -14.5t15.5 -6.5h16v-32h32v32h16q8 0 14.5 6.5t6.5 14.5zM245 160v64h43v-64h-43z" />
+<glyph unicode="&#xf3df;" d="M384 320q18 0 30.5 -12.5t12.5 -30.5v-256q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h42v128h171v-85h-128v-171h43v128h213z" />
+<glyph unicode="&#xf3e0;" horiz-adv-x="384" d="M256 256v-128h-128v128h128zM213 171v42h-42v-42h42zM384 213h-43v-42h43v-43h-43v-43q0 -17 -12.5 -29.5t-29.5 -12.5h-43v-43h-43v43h-42v-43h-43v43h-43q-17 0 -29.5 12.5t-12.5 29.5v43h-43v43h43v42h-43v43h43v43q0 17 12.5 29.5t29.5 12.5h43v43h43v-43h42v43h43 v-43h43q17 0 29.5 -12.5t12.5 -29.5v-43h43v-43zM299 85v214h-214v-214h214z" />
+<glyph unicode="&#xf3e1;" d="M384 405q18 0 30.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-30.5 -12.5h-341q-18 0 -30.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t30.5 12.5h341zM384 21v342h-341v-342h341zM341 320v-256h-256v256h86v-43h-43v-170h171v170h-64v-48q21 -12 21 -37q0 -18 -12.5 -30.5 t-30 -12.5t-30 12.5t-12.5 30.5q0 24 21 37v48q0 18 12.5 30.5t30.5 12.5h106z" />
+<glyph unicode="&#xf3e2;" horiz-adv-x="256" d="M107 341h42v-119h75l-96 -96l-96 96h75v119zM0 149h43q0 -35 25 -60t60 -25t60 25t25 60h43q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5z" />
+<glyph unicode="&#xf3e3;" horiz-adv-x="405" d="M0 256h405v-43h-405v43zM0 128v43h107v-43h-107zM149 128v43h107v-43h-107zM299 128v43h106v-43h-106z" />
+<glyph unicode="&#xf3e4;" horiz-adv-x="469" d="M427 384q17 0 29.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-29.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384zM427 42v300h-384v-300h384zM192 192h-43l86 85l85 -85h-43v-85h-85v85z" />
+<glyph unicode="&#xf3e5;" horiz-adv-x="384" d="M341 384q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-298q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h298zM43 342v-65q26 0 45 19t19 46h-64zM43 192q62 0 105.5 44t43.5 106h-43q0 -45 -31 -76t-75 -31v-43zM43 64h298l-96 128 l-74 -96l-54 64z" />
+<glyph unicode="&#xf3e6;" horiz-adv-x="363" d="M0 107q62 0 105.5 -44t43.5 -106h-42q0 44 -31.5 75.5t-75.5 31.5v43zM0 21q27 0 45.5 -18.5t18.5 -45.5h-64v64zM0 192q97 0 166 -68.5t69 -166.5h-43q0 80 -56 136t-136 56v43zM320 426q18 0 30.5 -12t12.5 -30v-363q0 -17 -12.5 -29.5t-30.5 -12.5h-44q-4 45 -21 85 h65v277h-213v-128q-20 8 -43 14v157q0 18 12.5 30.5t30.5 12.5z" />
+<glyph unicode="&#xf3e7;" horiz-adv-x="512" d="M0 128v128h43v-128h-43zM64 85v214h43v-214h-43zM469 256h43v-128h-43v128zM405 85v214h43v-214h-43zM352 384q13 0 22.5 -9.5t9.5 -22.5v-320q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v320q0 13 9.5 22.5t22.5 9.5h192zM341 43v298h-170v-298h170z " />
+<glyph unicode="&#xf3e8;" horiz-adv-x="512" d="M394.5 320q48.5 0 83 -34.5t34.5 -83t-34.5 -83t-82.5 -34.5h-278q-48 0 -82.5 34.5t-34.5 83t34.5 83t83 34.5t83 -34.5t34.5 -82.5q0 -43 -27 -75h96q-27 32 -27 75q0 48 34.5 82.5t83 34.5zM117 128q31 0 53 22t22 53t-22 52.5t-53 21.5t-52.5 -21.5t-21.5 -52.5 t21.5 -53t52.5 -22zM395 128q31 0 52.5 22t21.5 53t-21.5 52.5t-52.5 21.5t-53 -21.5t-22 -52.5t22 -53t53 -22z" />
+<glyph unicode="&#xf3e9;" horiz-adv-x="469" d="M149 277v-85h107v85h-107zM0 427h85v-22h299v22h85v-86h-21v-298h21v-86h-85v22h-299v-22h-85v86h21v298h-21v86zM85 43v-22h299v22h21v298h-21v22h-299v-22h-21v-298h21zM107 320h192v-85h64v-171h-214v85h-42v171zM299 149h-107v-42h128v85h-21v-43z" />
+<glyph unicode="&#xf3ea;" horiz-adv-x="341" d="M47 115q19 0 33 -13.5t14 -33t-14 -33.5t-33 -14t-33 14t-14 33.5t14 33t33 13.5zM0 238q90 0 153.5 -63.5t63.5 -153.5h-62q0 64 -45.5 109.5t-109.5 45.5v62zM0 363q93 0 171.5 -46t124 -124.5t45.5 -171.5h-62q0 116 -81.5 198t-197.5 82v62z" />
+<glyph unicode="&#xf3eb;" horiz-adv-x="474" d="M79 384h164q16 2 29.5 -9t14.5 -28v-55.5v-55.5l36 59q30 -47 84 -135.5t67 -110.5h-241q-29 -30 -52 -39q-42 -18 -88 -4t-71 53q-27 39 -21 89t41 82v106q-2 18 8.5 33t28.5 15zM91 331v-72q40 13 82 -1t65 -49v122h-147zM131 217q-30 -1 -51 -18.5t-31 -47.5 q-8 -34 13 -65.5t56 -36.5q34 -6 64 17t32 58q5 36 -21 65t-62 28zM323 201l-62 -102h125z" />
+<glyph unicode="&#xf3ec;" horiz-adv-x="423" d="M212.5 409q18.5 0 31.5 -13.5t13 -31.5t-13 -31.5t-31.5 -13.5t-31.5 13.5t-13 31.5t13 31.5t31.5 13.5zM337 362q22 0 37.5 -16t15.5 -37.5t-15.5 -37.5t-37.5 -16t-38 16t-16 37.5t16 37.5t38 16zM91.5 352q16.5 0 28 -12t11.5 -28.5t-11.5 -28.5t-28 -12t-28.5 12 t-12 28.5t12 28.5t28.5 12zM34 218q14 0 24 -10t10 -24t-10 -24t-24 -10t-24 10t-10 24t10 24t24 10zM389 218q14 0 24 -10t10 -24t-10 -24t-24 -10t-24 10t-10 24t10 24t24 10zM85 91q14 0 24 -10t10 -24t-10 -24t-24 -10t-24 10t-10 24t10 24t24 10zM340 91q14 0 24 -10 t10 -24t-10 -24t-24 -10t-24 10t-10 24t10 24t24 10zM212 44q14 0 24.5 -10.5t10.5 -24.5t-10.5 -24t-24.5 -10t-24 10t-10 24t10 24.5t24 10.5z" />
+<glyph unicode="&#xf3ed;" d="M0 405h85v-21h150v21h85v-85h-21v-64h42v21h86v-85h-22v-128h22v-85h-86v21h-128v-21h-85v85h21v43h-64v-22h-85v86h21v149h-21v85zM341 192v21h-42v-42h21v-86h-85v22h-43v-43h21v-21h128v21h22v128h-22zM235 320v21h-150v-21h-21v-149h21v-22h64v43h-21v85h85v-21h43 v64h-21zM213 192h-21v-43h43v22h21v42h-43v-21z" />
+<glyph unicode="&#xf3ee;" d="M281 137q-12 -13 -12 -14q-14 -13 -21 -17q-26 -19 -59 -12q-30 6 -45 36q-1 2 -2 3q-8 -12 -8 -13q-21 -28 -57 -29q-23 -1 -40 6q-36 16 -37 56h40l1 -4q5 -20 21 -24q21 -5 35 9q9 10 10.5 25t-6.5 26q-8 12 -23 14.5t-26 -6.5q-4 -3 -6 -5q-3 -5 -11 -5h-29 q9 53 20 110h111v-33h-4h-75q-3 0 -4 -3q-1 -4 -7 -39v-3q21 19 52 14q27 -5 42 -34q1 1 1 3q2 2 2 3q21 43 69 36q26 -3 46 -22q1 -1 23 -24l2 2q22 23 24 25q17 16 38 19q23 3 43 -5.5t30 -30.5q16 -38 -2 -74q-16 -33 -55 -35q-29 -1 -51 17q-3 2 -25 22q0 1 -5 6z M205 132q12 0 24 7q8 4 28 23q1 2 0 4q-2 1 -9.5 8t-11.5 11q-9 8 -21 12q-14 4 -25 -2t-15 -20q-1 -4 -1 -8q-1 -16 8 -25.5t23 -9.5zM304 164q22 -20 24 -21q13 -12 30 -11q25 0 30 24q1 7 0 15q-2 13 -11.5 21t-22.5 6q-15 -1 -28 -13q-1 -1 -22 -21z" />
+<glyph unicode="&#xf3ef;" d="M117 213h193q31 0 53 22t22 53t-22 53.5t-53.5 22.5t-53.5 -22.5t-22 -53.5v-33h-42v33q0 49 34.5 83.5t83 34.5t83 -34.5t34.5 -83t-34.5 -83t-82.5 -34.5h-193q-31 0 -53 -22t-22 -53t22 -53t53 -22t53 22t22 53v34h42v-34q0 -48 -34 -82.5t-82.5 -34.5t-83 34.5 t-34.5 83t34.5 82.5t82.5 34z" />
+<glyph unicode="&#xf3f0;" d="M379 87q6 2 9 -2.5t-2 -8.5q-34 -25 -81 -39t-92 -14q-122 0 -211 81q-3 3 -1 5.5t6 0.5q96 -56 211 -56q83 0 161 33zM425 113q5 -6 -2.5 -31.5t-23.5 -39.5q-3 -3 -5 -2t-1 5q18 45 12 53t-54 2q-4 0 -4.5 2t2.5 4q18 13 46 13.5t30 -6.5zM237 271v6q0 22 -6 30 q-7 11 -23 11q-28 0 -33 -25q-2 -8 -8 -8l-40 4q-8 2 -6 9q6 34 32.5 49t60.5 15q41 0 63 -21q3 -3 5.5 -6t4.5 -7.5t3.5 -7t2 -8t1.5 -8t1 -9v-8v-9v-9.5v-65q0 -17 16 -38q5 -7 0 -12q-16 -12 -32 -27q-5 -4 -10 -1q-11 9 -24 28q-17 -18 -32 -24.5t-37 -6.5 q-27 0 -44.5 17t-17.5 48q0 49 44 69q17 7 79 14zM229 184q8 14 8 45v9q-62 0 -62 -42q0 -14 6.5 -22.5t18.5 -8.5q18 0 29 19z" />
+<glyph unicode="&#xf3f1;" d="M397 243q30 0 30 -31v-103q0 -53 -39.5 -91.5t-92.5 -38.5h-171q-48 0 -86 38.5t-38 90.5v162q0 57 39 96t96 39h90q44 0 84.5 -39.5t40.5 -85.5v-11q0 -11 7.5 -18.5t20.5 -7.5h19zM135 294q-10 0 -17.5 -7.5t-7.5 -18t7.5 -18t17.5 -7.5h78q10 0 17 8t7 18t-7 17.5 t-17 7.5h-78zM289 90q10 0 17.5 6.5t7.5 16.5t-7.5 17t-17.5 7h-154q-10 0 -17.5 -7t-7.5 -17t7.5 -16.5t17.5 -6.5h154z" />
+<glyph unicode="&#xf3f2;" d="M427 192h-214v-213h-213v213h213v213h214v-213z" />
+<glyph unicode="&#xf3f3;" d="M219 243q26 0 42 -13t16 -38t-16 -38t-42 -13h-23v102h23zM221 400q85 0 145.5 -61t60.5 -147t-60.5 -147t-145.5 -61q-75 0 -133 49l-88 -12l34 85q-18 41 -18 86q0 86 60 147t145 61zM333 193v0q0 46 -30.5 74t-83.5 28h-78v-206h76q54 0 85 29t31 75z" />
+<glyph unicode="&#xf3f4;" d="M426 145q0 -46 -13 -81q-25 -66 -96 -81q-20 -4 -43 -4h-240h-3v1l45 45l124 124l0.5 0.5t1.5 0.5q4 4 7 3q4 -2 4 -8v-63v-4q0 -1 2 -1q47 1 55 1q8 1 19 5q28 9 35 42q3 16 3 33v114q0 3 3 6l93 93q0 1 2 4l1 -1h1v-4q0 -24 -1 -225zM100 112q0 -3 -3 -6l-94 -94l-3 -3 v5v111.5v112.5q0 45 12 79q25 69 99 84q19 4 42 4h75h89h75h4q-1 0 -2 -2q0 -1 -1 -1q-27 -28 -82.5 -83.5l-83.5 -83.5q-3 -2 -3 -3q-4 -3 -7 -1q-1 2 -4 6v3v32v31v3q0 1 -1 1q-50 -1 -59 -2q-4 0 -13 -3q-31 -9 -37 -44q-3 -15 -3 -34q-1 -25 0 -112z" />
+<glyph unicode="&#xf3f5;" d="M97 95q-40 0 -68.5 28.5t-28.5 68t28.5 67.5t68.5 28t68 -28t28 -67.5t-28 -68t-68 -28.5zM330 95q-40 0 -68.5 28.5t-28.5 68t28.5 67.5t68.5 28t68.5 -28t28.5 -67.5t-28.5 -68t-68.5 -28.5z" />
+<glyph unicode="&#xf3f6;" horiz-adv-x="384" d="M384 257q0 -38 -10.5 -65t-30.5 -41.5t-40 -21t-47 -9.5q19 -16 19 -51v-55.5v-34.5h-139v14v26.5v24.5q-16 -3 -29.5 -3t-23 2.5t-17 6.5t-12 8.5t-7.5 8.5t-4 7l-1 3q-6 14 -13.5 24t-12.5 13l-5 3q-11 9 -11 12.5t7 4.5h6q12 -1 23 -8t15 -14l5 -6q27 -47 81 -23 q3 24 18 37q-27 3 -47 9.5t-39.5 21t-30.5 41.5t-11 65q0 43 29 74q-13 33 3 74q3 -1 8 0.5t25 -6t44 -23.5q33 9 70 10q36 -1 70 -10q23 16 42.5 23t26.5 7l7 -1q17 -41 3 -74q29 -31 29 -74zM32 121.5q1 2.5 -2.5 4t-4.5 -1t2.5 -4t4.5 1zM43.5 109q2.5 2 -1 5.5t-6 1.5 t1 -5.5t6 -1.5zM54 93q3 2 0 6.5t-6 2.5t0 -6.5t6 -2.5zM69.5 77q2.5 3 -1.5 7.5t-7 1.5t1.5 -7.5t7 -1.5zM90 68.5q1 3.5 -4.5 5.5t-6.5 -2t4.5 -5.5t6.5 2zM107 63q6 0 6 4t-6 4t-6 -4t6 -4zM129 65q3 1 4.5 2.5t0.5 2.5q0 5 -6 4q-3 -1 -4.5 -2.5t-0.5 -3.5q0 -4 6 -3z " />
+<glyph unicode="&#xf3f7;" horiz-adv-x="267" d="M147 5q36 0 59 17.5t23 41.5q0 20 -12 33.5t-48 39.5h-14q-33 0 -59 -9q-48 -17 -48 -57q0 -30 27 -48t72 -18zM81 329q0 -36 19 -66.5t50 -30.5q17 0 34.5 12.5t17.5 42.5q0 33 -20 66t-52 33q-21 0 -35 -14.5t-14 -42.5zM220 164q22 -19 33.5 -36t11.5 -43 q0 -43 -38.5 -74.5t-107.5 -31.5q-58 0 -88.5 23.5t-30.5 57.5q0 43 42 67q39 24 107 29q-17 19 -17 36q0 6 7 23h-15q-41 0 -65.5 26.5t-24.5 60.5q0 44 31.5 73.5t88.5 29.5h113l-23 -22h-32q37 -32 37 -71q0 -19 -7.5 -34.5t-15.5 -23.5t-23 -20q-18 -14 -18 -29 q0 -13 15 -26z" />
+<glyph unicode="&#xf3f8;" d="M319 186.5q-8 10.5 -30 10.5q-27 0 -38 -16t-11 -45v-146q0 -5 -3 -8t-8 -3h-76q-4 0 -7.5 3t-3.5 8v270q0 4 3.5 7.5t7.5 3.5h74q4 0 6.5 -2t3.5 -6v-5q1 -2 1 -7q28 27 76 27q53 0 83 -27t30 -79v-182q0 -5 -3.5 -8t-7.5 -3h-78q-4 0 -7.5 3t-3.5 8v164q0 22 -8 32.5z M88 316.5q-15 -15.5 -36.5 -15.5t-36.5 15.5t-15 37t15 36.5t36.5 15t36.5 -15t15 -36.5t-15 -37zM101 260v-270q0 -5 -3.5 -8t-7.5 -3h-76q-5 0 -8 3t-3 8v270q0 4 3 7.5t8 3.5h76q4 0 7.5 -3.5t3.5 -7.5z" />
+<glyph unicode="&#xf3f9;" horiz-adv-x="256" d="M128 189q-45 0 -76.5 32t-31.5 76.5t31.5 76t76.5 31.5t76.5 -31.5t31.5 -76t-31.5 -76.5t-76.5 -32zM128 350q-22 0 -37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5zM252 176q8 -15 1 -24.5t-29 -24.5q-27 -17 -75 -22 l81 -81q7 -7 7 -17.5t-7 -17.5l-3 -3q-8 -7 -18 -7t-17 7q-12 11 -64 64l-63 -64q-7 -7 -17.5 -7t-17.5 7l-3 3q-7 7 -7 17.5t7 17.5l63 63l18 18q-48 4 -76 22q-22 15 -29 24.5t1 24.5q5 11 16 13.5t29 -8.5q14 -11 33.5 -17t32.5 -6l13 -1q49 0 79 24q18 11 29 8.5 t16 -13.5z" />
+<glyph unicode="&#xf3fa;" d="M267 315h145q4 0 9.5 -5t5.5 -12l-127 -85h-4l-29 18v84zM267 200l27 -18q2 -1 4 -1h3l1 1q-2 -1 29 19.5t64 41.5l32 21v-153q0 -12 -6.5 -18t-16.5 -6h-137v113v0zM128 239q13 0 20.5 -12.5t7.5 -34.5t-7.5 -34t-21.5 -12q-13 0 -21 12.5t-8 33.5t8 34t22 13zM0 357 l251 48v-426l-251 52v326zM168 139q16 21 16 54t-15.5 53.5t-40.5 20.5q-26 0 -42 -21t-16 -56q0 -32 16 -52t41 -20t41 21z" />
+<glyph unicode="&#xf3fb;" horiz-adv-x="356" d="M225 276q0 34 -52 34h-15q-7 0 -13 -4.5t-7 -11.5l-14 -60v-3q0 -5 3.5 -8.5t8.5 -3.5h12q15 0 28 3t24.5 9t18 17.5t6.5 27.5zM356 243q0 -58 -48 -93q-47 -35 -133 -35h-13q-7 0 -13 -4.5t-7 -11.5l-16 -69q-2 -7 -9 -12.5t-15 -5.5h-46q-7 0 -11.5 4t-4.5 11q0 2 3 14 h32q8 0 14.5 5t7.5 12l16 69q2 7 8.5 12t13.5 5h13q85 0 132 35t47 92q0 28 -11 44q40 -20 40 -72zM316 283q0 -57 -48 -93q-47 -35 -133 -35h-13q-7 0 -13 -4.5t-7 -11.5l-16 -68q-2 -8 -8.5 -13.5t-15.5 -5.5h-46q-7 0 -11.5 4t-4.5 11v4l66 283q1 7 8 12.5t15 5.5h97 q14 0 26.5 -0.5t26.5 -3t24.5 -6.5t21 -11t17 -16t10.5 -22.5t4 -29.5z" />
+<glyph unicode="&#xf3fc;" horiz-adv-x="341" d="M99 242q0 27 14 46t34 19q17 0 25.5 -11t8.5 -27q0 -10 -3 -25q-4 -14 -10 -34q-6 -19 -9 -31q-5 -20 7.5 -34.5t32.5 -14.5q35 0 57.5 39.5t22.5 95.5q0 43 -27.5 70t-77.5 27q-56 0 -90.5 -35.5t-34.5 -85.5q0 -29 17 -50q6 -6 4 -14q-2 -5 -5 -20q-2 -5 -5.5 -6.5 t-7.5 -0.5q-26 11 -39 37t-13 60q0 22 7 44t22 42.5t36 36.5t51 25.5t65 9.5t65.5 -12t51 -32t32 -46.5t11.5 -54.5q0 -75 -38 -124t-98 -49q-20 0 -37.5 9t-24.5 22q-15 -58 -18 -69q-8 -30 -36 -70h-17q-6 51 2 84l33 138q-8 17 -8 41z" />
+<glyph unicode="&#xf3fd;" d="M426 121q2 -9 -6.5 -18t-27.5 -15l-2 -1l-64 20l17 6q21 7 21 13q-2 10 -37 4l-36 -12l-61 -21v-22l96 32l64 -20l-99 -34l-61 -21v1v-1l-69 22v39v-19q-40 -14 -84 -6q-3 0 -11 1.5t-12 2t-11 1.5t-11.5 2.5t-10 3t-8.5 3t-6.5 4t-5 5t-1.5 5.5q-2 25 34 37l59 -18 l-15 -6q-15 -5 -6 -13q9 -9 25 -4l64 22v44l-27 8l27 9v177l88 -23q91 -24 90 -95q-1 -90 -82 -67v117q0 6 -7 9t-13.5 1t-6.5 -9v-148l6 2q58 20 104 17q80 -6 86 -35zM34 133h0.5h1.5l98 33l27 -8v-19l-68 -24z" />
+<glyph unicode="&#xf3fe;" d="M427 195q0 -28 -27 -39q2 -9 2 -19q0 -51 -55.5 -87.5t-134 -36.5t-134 36.5t-55.5 87.5q0 10 2 20q-25 11 -25 38q0 18 12.5 30.5t29.5 12.5q19 0 32 -15q52 36 129 39l35 104q3 7 10 5l83 -20q1 0 3 -1q8 20 30 20q13 0 23 -10t10 -23.5t-10 -23.5t-23 -10 q-14 0 -23.5 9.5t-9.5 23.5q-2 -1 -3 0l-77 18l-31 -92q79 -2 132 -40q13 16 33 16q17 0 29.5 -12.5t12.5 -30.5zM116 161.5q0 -12.5 9 -21.5t21.5 -9t21.5 9t9 21.5t-9 22t-21.5 9.5t-21.5 -9.5t-9 -22zM282 77q4 3 0.5 6.5t-7.5 0.5q-18 -19 -62 -19t-62 19 q-3 3 -6.5 -0.5t-0.5 -6.5q21 -22 70 -22q47 0 68 22zM280.5 131q12.5 0 21.5 9t9 21.5t-9 22t-21.5 9.5t-22 -9.5t-9.5 -22t9.5 -21.5t22 -9z" />
+<glyph unicode="&#xf3ff;" d="M414 147q13 -25 13 -52q0 -48 -34.5 -82t-83.5 -34q-30 0 -56 13q-19 -3 -37 -3q-85 0 -144.5 59t-59.5 142q0 20 4 40q-16 27 -16 59q0 48 34.5 82t83.5 34q34 0 63 -18q17 3 35 3q84 0 143.5 -58.5t59.5 -141.5q0 -22 -5 -43zM318.5 94.5q14.5 19.5 14.5 44.5 q0 21 -8.5 35.5t-23.5 23.5q-14 10 -34 16q-21 6 -45 12q-20 4 -29 7q-8 2 -16 6t-12 9t-4 12q0 11 12 19q14 8 36 8q23 0 34 -7q10 -8 18 -23q6 -11 12 -16t18 -5t20.5 8.5t8.5 19.5t-6.5 22.5t-20 22t-33.5 17t-47 6.5q-35 0 -60 -10q-26 -9 -39.5 -27t-13.5 -40 q0 -24 13 -41q13 -16 35 -25q21 -9 53 -16q23 -4 37 -9q14 -4 22 -11q8 -8 8 -20q0 -14 -15 -25q-16 -10 -41 -10q-18 0 -29.5 5t-17.5 13t-11 21q-5 11 -12 17q-8 6 -18 6q-13 0 -21.5 -8t-8.5 -19q0 -18 13 -36t34 -29q28 -15 72 -15q37 0 64 11t41.5 30.5z" />
+<glyph unicode="&#xf400;" horiz-adv-x="420" d="M153 276q21 0 35.5 -14t14.5 -33.5t-14.5 -33t-35.5 -13.5t-36 13.5t-15 33t15 33.5t36 14zM272 276q21 0 35.5 -13.5t14.5 -33t-14.5 -33.5t-35.5 -14t-36 14t-15 33.5t15 33t36 13.5zM403 202q10 7 15 0.5t-1 -15.5q-29 -36 -88 -60q26 -89 -22 -131q-32 -27 -64 -14 q-27 10 -26 42q0 -1 -0.5 24.5t-0.5 53.5l-4 1t-7 2v-36v-33t0 -12q1 -36 -32 -44q-36 -9 -65 23q-40 43 -16 124q-60 25 -89 60q-6 9 -1 15.5t14 -0.5l4 -3v165q0 17 12.5 29t28.5 12h300q16 0 26 -12t10 -29v-165q2 0 6 3zM376 186v159q0 22 -6.5 30.5t-24.5 8.5h-266 q-20 0 -26.5 -8.5t-6.5 -30.5v-160q23 -14 51 -19.5t46 -4.5t34 0q15 1 22 -6q1 0 1.5 -1l0.5 -1q9 -8 15 -12q1 22 27 20q16 1 34 0t46 5t53 20z" />
+<glyph unicode="&#xf401;" d="M9 124h-9v51h9v-51zM30 107h-9v81h9v-81zM47 103h-9v94h9v-94zM64 99h-9v94h9v-94zM85 99h-8v123h8v-123zM102 99h-8v140h8v-140zM124 99h-9v149h9v-149zM141 99h-9v153h9v-153zM162 99h-8v149h8v-149zM179 99h-8v145h8v-145zM196 99h-8v162h8v-162zM218 99h-9v175h9 v-175zM374 100h-146q-6 0 -6 6v167q0 4 5 6q17 6 34 6q36 0 62.5 -24.5t30.5 -59.5q9 4 20 4q22 0 37.5 -15.5t15.5 -37.5t-15.5 -37t-37.5 -15z" />
+<glyph unicode="&#xf402;" horiz-adv-x="265" d="M0 231v60q25 8 43 23.5t29 36.5t15 54h61v-108h102v-66h-102v-110q0 -37 4 -47.5t15 -16.5q14 -9 33 -9q32 0 65 21v-67q-28 -13 -50.5 -18t-48.5 -5q-29 0 -51.5 7t-38.5 21t-22.5 29.5t-6.5 44.5v149h-47v1z" />
+<glyph unicode="&#xf403;" horiz-adv-x="407" d="M296 34h-83l-56 -55h-55v55h-102v297l28 74h379v-259zM370 164v204h-305v-269h83v-55l56 55h102zM269 294h37v-111h-37v111zM167 183v111h37v-111h-37z" />
+<glyph unicode="&#xf404;" d="M276 284q-15 0 -32 -7q32 103 120 101q66 -2 62 -86q-2 -63 -87 -172q-87 -114 -147 -114q-37 0 -63 70q-18 66 -34 127q-19 69 -41 69q-5 0 -34 -20l-20 26q33 29 62 56q42 36 63 38q50 5 62 -68q12 -80 17 -99q14 -65 32 -65q13 0 40 42.5t29 64.5q3 37 -29 37z" />
+<glyph unicode="&#xf405;" d="M364.5 343q62.5 -62 62.5 -149.5t-62.5 -149.5t-150.5 -62v0q-54 0 -101 26l-113 -29l30 109q-28 49 -28 106q0 87 62 149t150 62t150.5 -62zM214 18q73 0 125 51.5t52 124.5t-52 124.5t-125 51.5t-124.5 -51.5t-51.5 -124.5q0 -51 27 -94l4 -6l-18 -65l67 17l6 -3 q42 -25 90 -25zM311 150q9 -5 10 -7q4 -6 -3 -25q-3 -8 -15 -15.5t-21 -9.5q-18 -2 -33 2q-17 6 -30 11q-8 4 -15.5 8.5t-14.5 9t-13 9.5t-11.5 10t-10.5 10.5t-8.5 9.5t-7 8.5t-5.5 7t-3.5 5l-1.5 2.5q-22 29 -22 55q0 24 19 44q6 7 14 7q6 0 10 -1q8 0 12 -9q2 -3 6 -13 l7 -17.5t3 -8.5q3 -5 1 -9q-3 -7 -5 -9l-3 -3t-3 -3.5t-2 -2.5q-6 -6 -3 -11q13 -22 30 -37q13 -11 43 -26q7 -3 11 1q12 15 17 21q4 6 12 3q6 -3 36 -17z" />
+<glyph unicode="&#xf406;" d="M213 323q-24 18 -47 27.5t-38.5 10.5t-28 0t-18.5 -4l-6 -3q59 51 138 51t139 -51q-3 1 -7 3t-17.5 4t-28.5 0t-38.5 -11t-47.5 -27v0zM157 282q-39 -40 -65 -78t-34.5 -63.5t-12 -44.5t-1.5 -28l3 -9q-47 58 -47 133q0 84 57 145q38 -16 100 -55zM427 192 q0 -75 -47 -133q1 3 2.5 9t-1.5 27.5t-12 45.5t-34.5 62.5t-65.5 78.5q28 17 53 31t36 19l11 5q58 -61 58 -145zM212 236q38 -27 67.5 -57t45 -53t26 -42t13.5 -29l3 -10q-62 -66 -153.5 -66t-154.5 66q2 4 5 11.5t15 30t28 44.5t44 51t61 54z" />
+<glyph unicode="&#xf407;" d="M414 271q-5 0 -49 -10q-10 -3 -62.5 -45.5t-56.5 -55.5q-2 -10 -2 -27l-1 -15q0 -9 4 -39q4 -1 32 -1t32 -1l-1 -20q-6 1 -105 1q-6 0 -44 -1t-49 -1l4 19h15.5t27 2t15.5 6q1 1 1.5 2t1 2.5t0.5 3v4.5v6v8v10v13q0 17 -1 27q-3 10 -51.5 69.5t-65.5 72.5q-3 1 -28.5 4 t-29.5 4l-1 18q2 1 17.5 1t35.5 -0.5t44 0.5q23 0 61 -0.5t45 -0.5l-3 -16q-4 -1 -30.5 -2.5t-31.5 -3.5q16 -24 50 -68.5t39 -51.5q2 3 41.5 36t40.5 43q-38 7 -54 7l-3 20h20h38.5h30.5q72 0 86 -2z" />
+<glyph unicode="&#xf408;" d="M422 277q5 -35 5 -69v-32l-5 -69q-4 -29 -17 -42q-14 -14 -42 -18q-27 -2 -64.5 -3t-61.5 -1h-24q-111 1 -145 4l-8 1t-13 2t-12.5 5t-13 10t-10 16.5t-5.5 18.5l-2 7q-4 35 -4 69v32l4 69q4 29 17 42q14 15 43 18q27 2 64 3t61 1h24q90 0 150 -4q28 -3 42 -18 q4 -4 7 -9.5t5 -11t3 -10.5t2 -8v-3zM271 189l14 7l-115 60v-120z" />
+<glyph unicode="&#xf409;" horiz-adv-x="352" d="M170 224q18 0 57.5 -1t59.5 -2q15 0 26 -3q28 -6 34 -40q5 -35 5 -59q0 -39 -3 -87q-1 -12 -7 -25q-11 -24 -43 -25q-103 -3 -152 -3q-17 0 -47.5 1t-40.5 1t-22 4q-20 5 -29 26q-6 17 -8 52q-1 41 2 94q1 15 5 31q9 31 42 33q31 0 121 3zM202 33q4 -3 13 -9 q9 -5 17.5 -1t10.5 15q2 9 2 14v60q0 8 -3 15q-3 13 -12 16.5t-20 -4.5q-2 -1 -4.5 -3.5l-3.5 -3.5v50h-21v-158h21q-1 4 0 9zM162 24v118h-22v-7v-76q0 -8 -6 -12q-4 -5 -9 -3q-3 1 -3 7v84v7h-22v-3v-96q0 -3 1 -9q4 -16 20 -11q4 1 13 7q2 1 6 6v-12h22zM323 79 q0 4 0.5 11t0.5 12.5t-1 10.5q-1 14 -9 22t-21 9q-14 1 -23.5 -6.5t-10.5 -21.5q-3 -33 0 -67q2 -15 14 -22.5t28 -3.5q13 3 19.5 15.5t2.5 26.5h-22q0 -10 -1 -14q-1 -9 -9 -9t-9 8q-1 9 -2 30q16 -1 43 -1zM103 159v23h-74v-23h24v-135h25v135h25zM247 364v-34v-59 q0 -6 4 -7q3 -1 7 2q7 5 7 14v26v31v27h22v-119h-22v12l-5 -5q-8 -6 -11 -7q-8 -4 -14.5 -0.5t-8.5 11.5q-1 4 -1 7v100q7 1 22 1zM66 405h22q2 0 4 -4q10 -34 14 -51q0 -1 2 -3q4 18 9 32q1 3 3 10.5t3 11.5q1 3 4 4h22q0 -1 -1 -4q-1 -1 -1 -2q-4 -14 -13 -42t-13 -42 q-2 -6 -2 -10v-59h-23q0 3 -0.5 8.5t-0.5 9.5t1 8q2 36 -13 78q-9 27 -17 55zM208 304v-11t-0.5 -13t-1.5 -11q-1 -11 -10 -18.5t-21 -7.5t-20 7.5t-11 18.5q-1 7 -1 21q0 38 1 49q4 27 31 27q26 0 31 -27q0 -1 0.5 -2.5t0.5 -2.5q0 -5 0.5 -15t0.5 -15zM167 305v-28 q0 -6 1 -8q4 -5 8 -5t8 5q1 2 1 8v49v12q-1 7 -9 7q-7 0 -9 -7q-1 -2 -1 -6t0.5 -13t0.5 -14zM206 83v31q0 9 11 9q8 0 8 -7v-5.5v-4.5v-50v-3.5v-3.5q-1 -7 -8 -7q-11 1 -11 9q-1 16 0 32zM299 97h-20q0 4 0.5 10.5t0.5 10.5q1 6 9 6q7 0 8 -6q2 -10 2 -21z" />
+</font>
+</defs></svg> \ No newline at end of file
diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.ttf b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf
new file mode 100755
index 000000000..5d489fdd1
--- /dev/null
+++ b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff b/packages/website/public/fonts/Material-Design-Iconic-Font.woff
new file mode 100755
index 000000000..933b2bf85
--- /dev/null
+++ b/packages/website/public/fonts/Material-Design-Iconic-Font.woff
Binary files differ
diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2
new file mode 100755
index 000000000..35970e277
--- /dev/null
+++ b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Black.ttf b/packages/website/public/fonts/Roboto-Black.ttf
new file mode 100755
index 000000000..689fe5cb3
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Black.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-BlackItalic.ttf b/packages/website/public/fonts/Roboto-BlackItalic.ttf
new file mode 100755
index 000000000..0b4e0ee10
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-BlackItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Bold.ttf b/packages/website/public/fonts/Roboto-Bold.ttf
new file mode 100755
index 000000000..d3f01ad24
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Bold.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-BoldItalic.ttf b/packages/website/public/fonts/Roboto-BoldItalic.ttf
new file mode 100755
index 000000000..41cc1e753
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-BoldItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Italic.ttf b/packages/website/public/fonts/Roboto-Italic.ttf
new file mode 100755
index 000000000..6a1cee5b2
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Italic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Light.ttf b/packages/website/public/fonts/Roboto-Light.ttf
new file mode 100755
index 000000000..219063a57
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Light.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-LightItalic.ttf b/packages/website/public/fonts/Roboto-LightItalic.ttf
new file mode 100755
index 000000000..0e81e876f
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-LightItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Medium.ttf b/packages/website/public/fonts/Roboto-Medium.ttf
new file mode 100755
index 000000000..1a7f3b0bb
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Medium.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-MediumItalic.ttf b/packages/website/public/fonts/Roboto-MediumItalic.ttf
new file mode 100755
index 000000000..003029527
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-MediumItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Regular.ttf b/packages/website/public/fonts/Roboto-Regular.ttf
new file mode 100755
index 000000000..2c97eeadf
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Regular.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-Thin.ttf b/packages/website/public/fonts/Roboto-Thin.ttf
new file mode 100755
index 000000000..b74a4fd1a
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-Thin.ttf
Binary files differ
diff --git a/packages/website/public/fonts/Roboto-ThinItalic.ttf b/packages/website/public/fonts/Roboto-ThinItalic.ttf
new file mode 100755
index 000000000..dd0ddb852
--- /dev/null
+++ b/packages/website/public/fonts/Roboto-ThinItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Bold.ttf b/packages/website/public/fonts/RobotoMono-Bold.ttf
new file mode 100755
index 000000000..07ef607d5
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Bold.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-BoldItalic.ttf b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf
new file mode 100755
index 000000000..1cca0bf45
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Italic.ttf b/packages/website/public/fonts/RobotoMono-Italic.ttf
new file mode 100755
index 000000000..ef92c372c
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Italic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Light.ttf b/packages/website/public/fonts/RobotoMono-Light.ttf
new file mode 100755
index 000000000..63229b280
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Light.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-LightItalic.ttf b/packages/website/public/fonts/RobotoMono-LightItalic.ttf
new file mode 100755
index 000000000..f25bed56a
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-LightItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Medium.ttf b/packages/website/public/fonts/RobotoMono-Medium.ttf
new file mode 100755
index 000000000..88ff0c15a
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Medium.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-MediumItalic.ttf b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf
new file mode 100755
index 000000000..307efad8f
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Regular.ttf b/packages/website/public/fonts/RobotoMono-Regular.ttf
new file mode 100755
index 000000000..b158a334e
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Regular.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-Thin.ttf b/packages/website/public/fonts/RobotoMono-Thin.ttf
new file mode 100755
index 000000000..309484d32
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-Thin.ttf
Binary files differ
diff --git a/packages/website/public/fonts/RobotoMono-ThinItalic.ttf b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf
new file mode 100755
index 000000000..e1bb9121e
--- /dev/null
+++ b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf
Binary files differ
diff --git a/packages/website/public/gifs/0xAnimation.gif b/packages/website/public/gifs/0xAnimation.gif
new file mode 100644
index 000000000..b3e32a6ad
--- /dev/null
+++ b/packages/website/public/gifs/0xAnimation.gif
Binary files differ
diff --git a/packages/website/public/gifs/genesis.gif b/packages/website/public/gifs/genesis.gif
new file mode 100644
index 000000000..009ecf2f8
--- /dev/null
+++ b/packages/website/public/gifs/genesis.gif
Binary files differ
diff --git a/packages/website/public/images/0x_logo.png b/packages/website/public/images/0x_logo.png
new file mode 100644
index 000000000..7b7eafe7d
--- /dev/null
+++ b/packages/website/public/images/0x_logo.png
Binary files differ
diff --git a/packages/website/public/images/advisors/fred.jpg b/packages/website/public/images/advisors/fred.jpg
new file mode 100644
index 000000000..f3b37e2d9
--- /dev/null
+++ b/packages/website/public/images/advisors/fred.jpg
Binary files differ
diff --git a/packages/website/public/images/advisors/joey.jpg b/packages/website/public/images/advisors/joey.jpg
new file mode 100644
index 000000000..daccc9b55
--- /dev/null
+++ b/packages/website/public/images/advisors/joey.jpg
Binary files differ
diff --git a/packages/website/public/images/advisors/linda.jpg b/packages/website/public/images/advisors/linda.jpg
new file mode 100644
index 000000000..1ee59d301
--- /dev/null
+++ b/packages/website/public/images/advisors/linda.jpg
Binary files differ
diff --git a/packages/website/public/images/advisors/olaf.png b/packages/website/public/images/advisors/olaf.png
new file mode 100644
index 000000000..d1715081f
--- /dev/null
+++ b/packages/website/public/images/advisors/olaf.png
Binary files differ
diff --git a/packages/website/public/images/ether.png b/packages/website/public/images/ether.png
new file mode 100644
index 000000000..6a40a976d
--- /dev/null
+++ b/packages/website/public/images/ether.png
Binary files differ
diff --git a/packages/website/public/images/favicon/favicon-2-16x16.png b/packages/website/public/images/favicon/favicon-2-16x16.png
new file mode 100755
index 000000000..68c493c4f
--- /dev/null
+++ b/packages/website/public/images/favicon/favicon-2-16x16.png
Binary files differ
diff --git a/packages/website/public/images/favicon/favicon-2-32x32.png b/packages/website/public/images/favicon/favicon-2-32x32.png
new file mode 100755
index 000000000..a5abb0eb3
--- /dev/null
+++ b/packages/website/public/images/favicon/favicon-2-32x32.png
Binary files differ
diff --git a/packages/website/public/images/favicon/favicon.ico b/packages/website/public/images/favicon/favicon.ico
new file mode 100755
index 000000000..b7ada2a1c
--- /dev/null
+++ b/packages/website/public/images/favicon/favicon.ico
Binary files differ
diff --git a/packages/website/public/images/landing/0x_chips.png b/packages/website/public/images/landing/0x_chips.png
new file mode 100644
index 000000000..2e79637b0
--- /dev/null
+++ b/packages/website/public/images/landing/0x_chips.png
Binary files differ
diff --git a/packages/website/public/images/landing/aragon.png b/packages/website/public/images/landing/aragon.png
new file mode 100644
index 000000000..360b03077
--- /dev/null
+++ b/packages/website/public/images/landing/aragon.png
Binary files differ
diff --git a/packages/website/public/images/landing/augur.png b/packages/website/public/images/landing/augur.png
new file mode 100644
index 000000000..60a2ee86b
--- /dev/null
+++ b/packages/website/public/images/landing/augur.png
Binary files differ
diff --git a/packages/website/public/images/landing/currency.png b/packages/website/public/images/landing/currency.png
new file mode 100644
index 000000000..280995861
--- /dev/null
+++ b/packages/website/public/images/landing/currency.png
Binary files differ
diff --git a/packages/website/public/images/landing/dharma.png b/packages/website/public/images/landing/dharma.png
new file mode 100644
index 000000000..5aa464c20
--- /dev/null
+++ b/packages/website/public/images/landing/dharma.png
Binary files differ
diff --git a/packages/website/public/images/landing/digital_goods.png b/packages/website/public/images/landing/digital_goods.png
new file mode 100644
index 000000000..bbe848441
--- /dev/null
+++ b/packages/website/public/images/landing/digital_goods.png
Binary files differ
diff --git a/packages/website/public/images/landing/distributed_network.png b/packages/website/public/images/landing/distributed_network.png
new file mode 100644
index 000000000..8dbb23083
--- /dev/null
+++ b/packages/website/public/images/landing/distributed_network.png
Binary files differ
diff --git a/packages/website/public/images/landing/ethfinex.png b/packages/website/public/images/landing/ethfinex.png
new file mode 100644
index 000000000..53e7913d6
--- /dev/null
+++ b/packages/website/public/images/landing/ethfinex.png
Binary files differ
diff --git a/packages/website/public/images/landing/fund_management_icon.png b/packages/website/public/images/landing/fund_management_icon.png
new file mode 100644
index 000000000..c45061469
--- /dev/null
+++ b/packages/website/public/images/landing/fund_management_icon.png
Binary files differ
diff --git a/packages/website/public/images/landing/gnosis.png b/packages/website/public/images/landing/gnosis.png
new file mode 100644
index 000000000..b9dd94f52
--- /dev/null
+++ b/packages/website/public/images/landing/gnosis.png
Binary files differ
diff --git a/packages/website/public/images/landing/governance_icon.png b/packages/website/public/images/landing/governance_icon.png
new file mode 100644
index 000000000..d21dc313d
--- /dev/null
+++ b/packages/website/public/images/landing/governance_icon.png
Binary files differ
diff --git a/packages/website/public/images/landing/hero_chip_image.png b/packages/website/public/images/landing/hero_chip_image.png
new file mode 100644
index 000000000..447489aa7
--- /dev/null
+++ b/packages/website/public/images/landing/hero_chip_image.png
Binary files differ
diff --git a/packages/website/public/images/landing/lendroid.png b/packages/website/public/images/landing/lendroid.png
new file mode 100644
index 000000000..3b8d0c86c
--- /dev/null
+++ b/packages/website/public/images/landing/lendroid.png
Binary files differ
diff --git a/packages/website/public/images/landing/liquidity.png b/packages/website/public/images/landing/liquidity.png
new file mode 100644
index 000000000..dc005dd13
--- /dev/null
+++ b/packages/website/public/images/landing/liquidity.png
Binary files differ
diff --git a/packages/website/public/images/landing/loans_icon.png b/packages/website/public/images/landing/loans_icon.png
new file mode 100644
index 000000000..b10bd83e9
--- /dev/null
+++ b/packages/website/public/images/landing/loans_icon.png
Binary files differ
diff --git a/packages/website/public/images/landing/maker.png b/packages/website/public/images/landing/maker.png
new file mode 100644
index 000000000..f7a04191e
--- /dev/null
+++ b/packages/website/public/images/landing/maker.png
Binary files differ
diff --git a/packages/website/public/images/landing/melonport.png b/packages/website/public/images/landing/melonport.png
new file mode 100644
index 000000000..bdb95707c
--- /dev/null
+++ b/packages/website/public/images/landing/melonport.png
Binary files differ
diff --git a/packages/website/public/images/landing/open_source.png b/packages/website/public/images/landing/open_source.png
new file mode 100644
index 000000000..2c280742a
--- /dev/null
+++ b/packages/website/public/images/landing/open_source.png
Binary files differ
diff --git a/packages/website/public/images/landing/paradex.png b/packages/website/public/images/landing/paradex.png
new file mode 100644
index 000000000..f6a1e479f
--- /dev/null
+++ b/packages/website/public/images/landing/paradex.png
Binary files differ
diff --git a/packages/website/public/images/landing/prediction_market_icon.png b/packages/website/public/images/landing/prediction_market_icon.png
new file mode 100644
index 000000000..063595c49
--- /dev/null
+++ b/packages/website/public/images/landing/prediction_market_icon.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/anx.png b/packages/website/public/images/landing/project_logos/anx.png
new file mode 100644
index 000000000..fa04ce327
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/anx.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/aragon.png b/packages/website/public/images/landing/project_logos/aragon.png
new file mode 100644
index 000000000..b0cf805d3
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/aragon.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/auctus.png b/packages/website/public/images/landing/project_logos/auctus.png
new file mode 100644
index 000000000..2bdc9a42c
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/auctus.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/augur.png b/packages/website/public/images/landing/project_logos/augur.png
new file mode 100644
index 000000000..47d4f8465
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/augur.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/blocknet.png b/packages/website/public/images/landing/project_logos/blocknet.png
new file mode 100644
index 000000000..96aa97953
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/blocknet.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/chronobank.png b/packages/website/public/images/landing/project_logos/chronobank.png
new file mode 100644
index 000000000..ad5ff0e5b
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/chronobank.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/dharma.png b/packages/website/public/images/landing/project_logos/dharma.png
new file mode 100644
index 000000000..a74736ca2
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/dharma.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/district0x.png b/packages/website/public/images/landing/project_logos/district0x.png
new file mode 100644
index 000000000..12aa310f0
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/district0x.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/dydx.png b/packages/website/public/images/landing/project_logos/dydx.png
new file mode 100644
index 000000000..f2525292c
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/dydx.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/ethfinex-top.png b/packages/website/public/images/landing/project_logos/ethfinex-top.png
new file mode 100644
index 000000000..5eda914ca
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/ethfinex-top.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/ethix.png b/packages/website/public/images/landing/project_logos/ethix.png
new file mode 100644
index 000000000..9b202502c
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/ethix.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/lendroid.png b/packages/website/public/images/landing/project_logos/lendroid.png
new file mode 100644
index 000000000..a5e1eb51e
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/lendroid.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/maker.png b/packages/website/public/images/landing/project_logos/maker.png
new file mode 100644
index 000000000..d3bb927a5
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/maker.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/melonport.png b/packages/website/public/images/landing/project_logos/melonport.png
new file mode 100644
index 000000000..9533faee2
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/melonport.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/paradex_top.png b/packages/website/public/images/landing/project_logos/paradex_top.png
new file mode 100644
index 000000000..3fe9472b9
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/paradex_top.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/radar_relay_top.png b/packages/website/public/images/landing/project_logos/radar_relay_top.png
new file mode 100644
index 000000000..737159959
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/radar_relay_top.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/status.png b/packages/website/public/images/landing/project_logos/status.png
new file mode 100644
index 000000000..24c7e177a
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/status.png
Binary files differ
diff --git a/packages/website/public/images/landing/project_logos/the_ocean.png b/packages/website/public/images/landing/project_logos/the_ocean.png
new file mode 100644
index 000000000..74678b5d4
--- /dev/null
+++ b/packages/website/public/images/landing/project_logos/the_ocean.png
Binary files differ
diff --git a/packages/website/public/images/landing/radar_relay.png b/packages/website/public/images/landing/radar_relay.png
new file mode 100644
index 000000000..297f55cfb
--- /dev/null
+++ b/packages/website/public/images/landing/radar_relay.png
Binary files differ
diff --git a/packages/website/public/images/landing/relayer_diagram.png b/packages/website/public/images/landing/relayer_diagram.png
new file mode 100644
index 000000000..44c4ef707
--- /dev/null
+++ b/packages/website/public/images/landing/relayer_diagram.png
Binary files differ
diff --git a/packages/website/public/images/landing/stable_tokens_icon.png b/packages/website/public/images/landing/stable_tokens_icon.png
new file mode 100644
index 000000000..40e372606
--- /dev/null
+++ b/packages/website/public/images/landing/stable_tokens_icon.png
Binary files differ
diff --git a/packages/website/public/images/landing/stocks.png b/packages/website/public/images/landing/stocks.png
new file mode 100644
index 000000000..e244cd0c5
--- /dev/null
+++ b/packages/website/public/images/landing/stocks.png
Binary files differ
diff --git a/packages/website/public/images/landing/tokenized_world.png b/packages/website/public/images/landing/tokenized_world.png
new file mode 100644
index 000000000..b284eaa88
--- /dev/null
+++ b/packages/website/public/images/landing/tokenized_world.png
Binary files differ
diff --git a/packages/website/public/images/loading_poster.png b/packages/website/public/images/loading_poster.png
new file mode 100644
index 000000000..e5618f260
--- /dev/null
+++ b/packages/website/public/images/loading_poster.png
Binary files differ
diff --git a/packages/website/public/images/logos/FBG.png b/packages/website/public/images/logos/FBG.png
new file mode 100644
index 000000000..0a91bcabc
--- /dev/null
+++ b/packages/website/public/images/logos/FBG.png
Binary files differ
diff --git a/packages/website/public/images/logos/aragon.png b/packages/website/public/images/logos/aragon.png
new file mode 100644
index 000000000..db4d81905
--- /dev/null
+++ b/packages/website/public/images/logos/aragon.png
Binary files differ
diff --git a/packages/website/public/images/logos/augur.png b/packages/website/public/images/logos/augur.png
new file mode 100644
index 000000000..709da2f1a
--- /dev/null
+++ b/packages/website/public/images/logos/augur.png
Binary files differ
diff --git a/packages/website/public/images/logos/blockchain_capital.png b/packages/website/public/images/logos/blockchain_capital.png
new file mode 100644
index 000000000..42fdcbfa1
--- /dev/null
+++ b/packages/website/public/images/logos/blockchain_capital.png
Binary files differ
diff --git a/packages/website/public/images/logos/chronobank.png b/packages/website/public/images/logos/chronobank.png
new file mode 100644
index 000000000..f94aa3fee
--- /dev/null
+++ b/packages/website/public/images/logos/chronobank.png
Binary files differ
diff --git a/packages/website/public/images/logos/dharma.png b/packages/website/public/images/logos/dharma.png
new file mode 100644
index 000000000..65d902a1e
--- /dev/null
+++ b/packages/website/public/images/logos/dharma.png
Binary files differ
diff --git a/packages/website/public/images/logos/district0x.png b/packages/website/public/images/logos/district0x.png
new file mode 100644
index 000000000..e2b4c7e4a
--- /dev/null
+++ b/packages/website/public/images/logos/district0x.png
Binary files differ
diff --git a/packages/website/public/images/logos/jen_advisors.png b/packages/website/public/images/logos/jen_advisors.png
new file mode 100644
index 000000000..de0395d3d
--- /dev/null
+++ b/packages/website/public/images/logos/jen_advisors.png
Binary files differ
diff --git a/packages/website/public/images/logos/maker.png b/packages/website/public/images/logos/maker.png
new file mode 100644
index 000000000..48c08d15d
--- /dev/null
+++ b/packages/website/public/images/logos/maker.png
Binary files differ
diff --git a/packages/website/public/images/logos/melonport.png b/packages/website/public/images/logos/melonport.png
new file mode 100644
index 000000000..b973e081f
--- /dev/null
+++ b/packages/website/public/images/logos/melonport.png
Binary files differ
diff --git a/packages/website/public/images/logos/openANX.png b/packages/website/public/images/logos/openANX.png
new file mode 100644
index 000000000..e0167257f
--- /dev/null
+++ b/packages/website/public/images/logos/openANX.png
Binary files differ
diff --git a/packages/website/public/images/logos/pantera_capital.png b/packages/website/public/images/logos/pantera_capital.png
new file mode 100644
index 000000000..9cffdf39f
--- /dev/null
+++ b/packages/website/public/images/logos/pantera_capital.png
Binary files differ
diff --git a/packages/website/public/images/logos/polychain_capital.png b/packages/website/public/images/logos/polychain_capital.png
new file mode 100644
index 000000000..2b7782134
--- /dev/null
+++ b/packages/website/public/images/logos/polychain_capital.png
Binary files differ
diff --git a/packages/website/public/images/og_image.png b/packages/website/public/images/og_image.png
new file mode 100644
index 000000000..74265bdf8
--- /dev/null
+++ b/packages/website/public/images/og_image.png
Binary files differ
diff --git a/packages/website/public/images/protocol_logo_black.png b/packages/website/public/images/protocol_logo_black.png
new file mode 100644
index 000000000..36a905d5a
--- /dev/null
+++ b/packages/website/public/images/protocol_logo_black.png
Binary files differ
diff --git a/packages/website/public/images/protocol_logo_white.png b/packages/website/public/images/protocol_logo_white.png
new file mode 100644
index 000000000..a405ad6d5
--- /dev/null
+++ b/packages/website/public/images/protocol_logo_white.png
Binary files differ
diff --git a/packages/website/public/images/social/github.png b/packages/website/public/images/social/github.png
new file mode 100644
index 000000000..2c2a3e918
--- /dev/null
+++ b/packages/website/public/images/social/github.png
Binary files differ
diff --git a/packages/website/public/images/social/medium.png b/packages/website/public/images/social/medium.png
new file mode 100644
index 000000000..11e8f2c44
--- /dev/null
+++ b/packages/website/public/images/social/medium.png
Binary files differ
diff --git a/packages/website/public/images/social/reddit.png b/packages/website/public/images/social/reddit.png
new file mode 100644
index 000000000..3fbe6229a
--- /dev/null
+++ b/packages/website/public/images/social/reddit.png
Binary files differ
diff --git a/packages/website/public/images/social/rocketchat.png b/packages/website/public/images/social/rocketchat.png
new file mode 100644
index 000000000..58ff8d293
--- /dev/null
+++ b/packages/website/public/images/social/rocketchat.png
Binary files differ
diff --git a/packages/website/public/images/social/slack.png b/packages/website/public/images/social/slack.png
new file mode 100644
index 000000000..c4b2d7b81
--- /dev/null
+++ b/packages/website/public/images/social/slack.png
Binary files differ
diff --git a/packages/website/public/images/social/twitter.png b/packages/website/public/images/social/twitter.png
new file mode 100644
index 000000000..fe0d691a9
--- /dev/null
+++ b/packages/website/public/images/social/twitter.png
Binary files differ
diff --git a/packages/website/public/images/team/alex.jpg b/packages/website/public/images/team/alex.jpg
new file mode 100644
index 000000000..ae6888804
--- /dev/null
+++ b/packages/website/public/images/team/alex.jpg
Binary files differ
diff --git a/packages/website/public/images/team/amir.jpeg b/packages/website/public/images/team/amir.jpeg
new file mode 100644
index 000000000..7ee16263a
--- /dev/null
+++ b/packages/website/public/images/team/amir.jpeg
Binary files differ
diff --git a/packages/website/public/images/team/anyone.png b/packages/website/public/images/team/anyone.png
new file mode 100644
index 000000000..4de26b0ce
--- /dev/null
+++ b/packages/website/public/images/team/anyone.png
Binary files differ
diff --git a/packages/website/public/images/team/ben.jpg b/packages/website/public/images/team/ben.jpg
new file mode 100644
index 000000000..b42d0a42a
--- /dev/null
+++ b/packages/website/public/images/team/ben.jpg
Binary files differ
diff --git a/packages/website/public/images/team/brandon.png b/packages/website/public/images/team/brandon.png
new file mode 100644
index 000000000..ebd3cf101
--- /dev/null
+++ b/packages/website/public/images/team/brandon.png
Binary files differ
diff --git a/packages/website/public/images/team/fabio.jpg b/packages/website/public/images/team/fabio.jpg
new file mode 100644
index 000000000..da87a9e95
--- /dev/null
+++ b/packages/website/public/images/team/fabio.jpg
Binary files differ
diff --git a/packages/website/public/images/team/leonid.png b/packages/website/public/images/team/leonid.png
new file mode 100644
index 000000000..4acbf87c8
--- /dev/null
+++ b/packages/website/public/images/team/leonid.png
Binary files differ
diff --git a/packages/website/public/images/team/philippe.png b/packages/website/public/images/team/philippe.png
new file mode 100644
index 000000000..cd43c2754
--- /dev/null
+++ b/packages/website/public/images/team/philippe.png
Binary files differ
diff --git a/packages/website/public/images/team/will.jpg b/packages/website/public/images/team/will.jpg
new file mode 100644
index 000000000..7de028032
--- /dev/null
+++ b/packages/website/public/images/team/will.jpg
Binary files differ
diff --git a/packages/website/public/images/token_icons/adtoken.png b/packages/website/public/images/token_icons/adtoken.png
new file mode 100644
index 000000000..59290af6b
--- /dev/null
+++ b/packages/website/public/images/token_icons/adtoken.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/aragon.png b/packages/website/public/images/token_icons/aragon.png
new file mode 100644
index 000000000..d162aab24
--- /dev/null
+++ b/packages/website/public/images/token_icons/aragon.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/augur.png b/packages/website/public/images/token_icons/augur.png
new file mode 100644
index 000000000..31c257ba9
--- /dev/null
+++ b/packages/website/public/images/token_icons/augur.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/bancor.png b/packages/website/public/images/token_icons/bancor.png
new file mode 100644
index 000000000..d2b2fa472
--- /dev/null
+++ b/packages/website/public/images/token_icons/bancor.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/basicattentiontoken.png b/packages/website/public/images/token_icons/basicattentiontoken.png
new file mode 100644
index 000000000..77e7dfb1f
--- /dev/null
+++ b/packages/website/public/images/token_icons/basicattentiontoken.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/bitquence.png b/packages/website/public/images/token_icons/bitquence.png
new file mode 100644
index 000000000..d8a2c6960
--- /dev/null
+++ b/packages/website/public/images/token_icons/bitquence.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/btc.png b/packages/website/public/images/token_icons/btc.png
new file mode 100644
index 000000000..1d9fc8347
--- /dev/null
+++ b/packages/website/public/images/token_icons/btc.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/civic.png b/packages/website/public/images/token_icons/civic.png
new file mode 100644
index 000000000..1daf28d00
--- /dev/null
+++ b/packages/website/public/images/token_icons/civic.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/clams.png b/packages/website/public/images/token_icons/clams.png
new file mode 100644
index 000000000..04c2ba7d3
--- /dev/null
+++ b/packages/website/public/images/token_icons/clams.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/cofound-it.png b/packages/website/public/images/token_icons/cofound-it.png
new file mode 100644
index 000000000..7bccd6248
--- /dev/null
+++ b/packages/website/public/images/token_icons/cofound-it.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/default.png b/packages/website/public/images/token_icons/default.png
new file mode 100644
index 000000000..7ce754030
--- /dev/null
+++ b/packages/website/public/images/token_icons/default.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/digixdao.png b/packages/website/public/images/token_icons/digixdao.png
new file mode 100644
index 000000000..f292db716
--- /dev/null
+++ b/packages/website/public/images/token_icons/digixdao.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/district0x.png b/packages/website/public/images/token_icons/district0x.png
new file mode 100644
index 000000000..edb8f2238
--- /dev/null
+++ b/packages/website/public/images/token_icons/district0x.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/edgeless.png b/packages/website/public/images/token_icons/edgeless.png
new file mode 100644
index 000000000..606784154
--- /dev/null
+++ b/packages/website/public/images/token_icons/edgeless.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/eos.png b/packages/website/public/images/token_icons/eos.png
new file mode 100644
index 000000000..a08f3c042
--- /dev/null
+++ b/packages/website/public/images/token_icons/eos.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/ether_erc20.png b/packages/website/public/images/token_icons/ether_erc20.png
new file mode 100644
index 000000000..f4154db7b
--- /dev/null
+++ b/packages/website/public/images/token_icons/ether_erc20.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/etheroll.png b/packages/website/public/images/token_icons/etheroll.png
new file mode 100644
index 000000000..89dd5e04b
--- /dev/null
+++ b/packages/website/public/images/token_icons/etheroll.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/firstblood.jpg b/packages/website/public/images/token_icons/firstblood.jpg
new file mode 100644
index 000000000..64bb65605
--- /dev/null
+++ b/packages/website/public/images/token_icons/firstblood.jpg
Binary files differ
diff --git a/packages/website/public/images/token_icons/funfair.png b/packages/website/public/images/token_icons/funfair.png
new file mode 100644
index 000000000..1b7c67ec6
--- /dev/null
+++ b/packages/website/public/images/token_icons/funfair.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/gnosis.png b/packages/website/public/images/token_icons/gnosis.png
new file mode 100644
index 000000000..0111846d0
--- /dev/null
+++ b/packages/website/public/images/token_icons/gnosis.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/golem.png b/packages/website/public/images/token_icons/golem.png
new file mode 100644
index 000000000..e61a4367d
--- /dev/null
+++ b/packages/website/public/images/token_icons/golem.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/iconomi.png b/packages/website/public/images/token_icons/iconomi.png
new file mode 100644
index 000000000..3499e4765
--- /dev/null
+++ b/packages/website/public/images/token_icons/iconomi.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/iexec.png b/packages/website/public/images/token_icons/iexec.png
new file mode 100644
index 000000000..ef4860457
--- /dev/null
+++ b/packages/website/public/images/token_icons/iexec.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/lunyr.png b/packages/website/public/images/token_icons/lunyr.png
new file mode 100644
index 000000000..f77094ba5
--- /dev/null
+++ b/packages/website/public/images/token_icons/lunyr.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/makerdao.png b/packages/website/public/images/token_icons/makerdao.png
new file mode 100644
index 000000000..adbc9f38c
--- /dev/null
+++ b/packages/website/public/images/token_icons/makerdao.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/melon.png b/packages/website/public/images/token_icons/melon.png
new file mode 100644
index 000000000..29f58e631
--- /dev/null
+++ b/packages/website/public/images/token_icons/melon.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/metal.png b/packages/website/public/images/token_icons/metal.png
new file mode 100644
index 000000000..d8a8c33ec
--- /dev/null
+++ b/packages/website/public/images/token_icons/metal.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/monaco.png b/packages/website/public/images/token_icons/monaco.png
new file mode 100644
index 000000000..865341fd3
--- /dev/null
+++ b/packages/website/public/images/token_icons/monaco.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/numeraire.png b/packages/website/public/images/token_icons/numeraire.png
new file mode 100644
index 000000000..698f7cfdd
--- /dev/null
+++ b/packages/website/public/images/token_icons/numeraire.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/omisego.png b/packages/website/public/images/token_icons/omisego.png
new file mode 100644
index 000000000..40a86b9d7
--- /dev/null
+++ b/packages/website/public/images/token_icons/omisego.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/qtum.png b/packages/website/public/images/token_icons/qtum.png
new file mode 100644
index 000000000..97e227c5c
--- /dev/null
+++ b/packages/website/public/images/token_icons/qtum.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/santiment.png b/packages/website/public/images/token_icons/santiment.png
new file mode 100644
index 000000000..05ce98c1d
--- /dev/null
+++ b/packages/website/public/images/token_icons/santiment.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/singularity.png b/packages/website/public/images/token_icons/singularity.png
new file mode 100644
index 000000000..9db788935
--- /dev/null
+++ b/packages/website/public/images/token_icons/singularity.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/status.png b/packages/website/public/images/token_icons/status.png
new file mode 100644
index 000000000..a73ba23ba
--- /dev/null
+++ b/packages/website/public/images/token_icons/status.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/storjcoinx.png b/packages/website/public/images/token_icons/storjcoinx.png
new file mode 100644
index 000000000..87c4d4292
--- /dev/null
+++ b/packages/website/public/images/token_icons/storjcoinx.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/taas.png b/packages/website/public/images/token_icons/taas.png
new file mode 100644
index 000000000..4cca722f7
--- /dev/null
+++ b/packages/website/public/images/token_icons/taas.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/tenx.png b/packages/website/public/images/token_icons/tenx.png
new file mode 100644
index 000000000..d9ffca043
--- /dev/null
+++ b/packages/website/public/images/token_icons/tenx.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/tokencard.png b/packages/website/public/images/token_icons/tokencard.png
new file mode 100644
index 000000000..490c1be69
--- /dev/null
+++ b/packages/website/public/images/token_icons/tokencard.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/trust.png b/packages/website/public/images/token_icons/trust.png
new file mode 100644
index 000000000..62b412b41
--- /dev/null
+++ b/packages/website/public/images/token_icons/trust.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/wings.png b/packages/website/public/images/token_icons/wings.png
new file mode 100644
index 000000000..cd0eb4213
--- /dev/null
+++ b/packages/website/public/images/token_icons/wings.png
Binary files differ
diff --git a/packages/website/public/images/token_icons/zero_ex.png b/packages/website/public/images/token_icons/zero_ex.png
new file mode 100644
index 000000000..6c6deef11
--- /dev/null
+++ b/packages/website/public/images/token_icons/zero_ex.png
Binary files differ
diff --git a/packages/website/public/images/trade_arrows.png b/packages/website/public/images/trade_arrows.png
new file mode 100644
index 000000000..21a8e1881
--- /dev/null
+++ b/packages/website/public/images/trade_arrows.png
Binary files differ
diff --git a/packages/website/public/images/zrx_pie_chart.png b/packages/website/public/images/zrx_pie_chart.png
new file mode 100644
index 000000000..16f5126b9
--- /dev/null
+++ b/packages/website/public/images/zrx_pie_chart.png
Binary files differ
diff --git a/packages/website/public/images/zrx_token.png b/packages/website/public/images/zrx_token.png
new file mode 100644
index 000000000..8c71798de
--- /dev/null
+++ b/packages/website/public/images/zrx_token.png
Binary files differ
diff --git a/packages/website/public/index.html b/packages/website/public/index.html
new file mode 100644
index 000000000..c6f2f666c
--- /dev/null
+++ b/packages/website/public/index.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" />
+ <meta property="og:type" content="website" />
+ <meta property="og:title" content="0x" />
+ <meta property="og:description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" />
+ <meta property="og:image" content="/images/og_image.png" />
+ <title>0x: The Protocol for Trading Tokens</title>
+ <link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" />
+ <link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" />
+ <link rel="stylesheet" href="/css/atom-one-light.css">
+ <link rel="stylesheet" href="/css/material-design-iconic-font.min.css">
+ <link rel="stylesheet" href="/css/roboto.css">
+ <link rel="stylesheet" href="/css/roboto_mono.css">
+ <link rel="stylesheet" href="/css/basscss_responsive_custom.css">
+ <link rel="stylesheet" href="/css/basscss_responsive_padding.css">
+ <link rel="stylesheet" href="/css/basscss_responsive_margin.css">
+ <link rel="stylesheet" href="/css/basscss_responsive_type_scale.css">
+ </head>
+ <body style="margin: 0px; min-width: 355px;">
+ <!-- Google Analytics -->
+ <script>
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+ ga('create', 'UA-98720122-1', 'auto');
+ ga('send', 'pageview');
+ </script>
+ <!-- End Google Analytics -->
+ <!-- Facebook SDK -->
+ <div id="fb-root"></div>
+ <script>
+ (function(d, s, id) {
+ var js, fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) return;
+ js = d.createElement(s); js.id = id;
+ js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1687545238205192";
+ fjs.parentNode.insertBefore(js, fjs);
+ }(document, 'script', 'facebook-jssdk'));
+ </script>
+ <div id="app"></div>
+ <!-- End Facebook SDK -->
+ <!-- Twitter SDK -->
+ <script>
+ window.twttr = (function(d, s, id) {
+ var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {};
+ if (d.getElementById(id)) return t;
+ js = d.createElement(s);
+ js.id = id;
+ js.src = "https://platform.twitter.com/widgets.js";
+ fjs.parentNode.insertBefore(js, fjs);
+
+ t._e = [];
+ t.ready = function(f) {
+ t._e.push(f);
+ };
+ return t;
+ }(document, "script", "twitter-wjs"));
+ </script>
+ <!-- End Twitter SDK -->
+ <!-- Segment.io -->
+ <script>
+ !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="4.0.0";
+ analytics.load("T6jtT2F2iMrw9FDJ8exE9Uu1mLN5qd8n");
+ analytics.page();
+ }}();
+ </script>
+ <!-- End Segment.io -->
+
+ <!-- Main -->
+ <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
+ </body>
+</html>
diff --git a/packages/website/public/js/rollbar.umd.nojson.min.js b/packages/website/public/js/rollbar.umd.nojson.min.js
new file mode 100644
index 000000000..f2b05d00a
--- /dev/null
+++ b/packages/website/public/js/rollbar.umd.nojson.min.js
@@ -0,0 +1 @@
+!function(e,r){if("object"==typeof exports&&"object"==typeof module)module.exports=r();else if("function"==typeof define&&define.amd)define([],r);else{var t=r();for(var n in t)("object"==typeof exports?exports:e)[n]=t[n]}}(this,function(){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){e.exports=t(1)},function(e,r,t){"use strict";function n(){var e="undefined"==typeof JSON?{}:JSON;o.setupJSON(e)}var o=t(2),i=t(3);n();var a=window._rollbarConfig,s=a&&a.globalAlias||"Rollbar",u=window[s]&&"undefined"!=typeof window[s].shimId;!u&&a?o.wrapper.init(a):(window.Rollbar=o.wrapper,window.RollbarNotifier=i.Notifier),e.exports=o.wrapper},function(e,r,t){"use strict";function n(e,r,t){!t[4]&&window._rollbarWrappedError&&(t[4]=window._rollbarWrappedError,window._rollbarWrappedError=null),e.uncaughtError.apply(e,t),r&&r.apply(window,t)}function o(e,r){if(r.hasOwnProperty&&r.hasOwnProperty("addEventListener")){var t=r.addEventListener;r.addEventListener=function(r,n,o){t.call(this,r,e.wrap(n),o)};var n=r.removeEventListener;r.removeEventListener=function(e,r,t){n.call(this,e,r&&r._wrapped||r,t)}}}var i=t(3),a=t(8),s=i.Notifier;window._rollbarWrappedError=null;var u={};u.init=function(e,r){var t=new s(r);if(t.configure(e),e.captureUncaught){var i;r&&a.isType(r._rollbarOldOnError,"function")?i=r._rollbarOldOnError:window.onerror&&!window.onerror.belongsToShim&&(i=window.onerror),window.onerror=function(){var e=Array.prototype.slice.call(arguments,0);n(t,i,e)};var u,c,l=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"];for(u=0;u<l.length;++u)c=l[u],window[c]&&window[c].prototype&&o(t,window[c].prototype)}return e.captureUnhandledRejections&&(r&&a.isType(r._unhandledRejectionHandler,"function")&&window.removeEventListener("unhandledrejection",r._unhandledRejectionHandler),t._unhandledRejectionHandler=function(e){var r=e.reason,n=e.promise,o=e.detail;!r&&o&&(r=o.reason,n=o.promise),t.unhandledRejection(r,n)},window.addEventListener("unhandledrejection",t._unhandledRejectionHandler)),window.Rollbar=t,s.processPayloads(),t},e.exports={wrapper:u,setupJSON:i.setupJSON}},function(e,r,t){"use strict";function n(e){E=e,w.setupJSON(e),v.setupJSON(e)}function o(e,r){return function(){var t=r||this;try{return e.apply(t,arguments)}catch(e){v.consoleError("[Rollbar]:",e)}}}function i(){h||(h=setTimeout(f,1e3))}function a(){return _}function s(e){_=_||this;var r="https://"+s.DEFAULT_ENDPOINT;this.options={enabled:!0,endpoint:r,environment:"production",scrubFields:g([],s.DEFAULT_SCRUB_FIELDS),checkIgnore:null,logLevel:s.DEFAULT_LOG_LEVEL,reportLevel:s.DEFAULT_REPORT_LEVEL,uncaughtErrorLevel:s.DEFAULT_UNCAUGHT_ERROR_LEVEL,payload:{}},this.lastError=null,this.plugins={},this.parentNotifier=e,e&&(e.hasOwnProperty("shimId")?e.notifier=this:this.configure(e.options))}function u(e){window._rollbarPayloadQueue.push(e),i()}function c(e){return o(function(){var r=this._getLogArgs(arguments);return this._log(e||r.level||this.options.logLevel||s.DEFAULT_LOG_LEVEL,r.message,r.err,r.custom,r.callback)})}function l(e,r){e||(e=r?E.stringify(r):"");var t={body:e};return r&&(t.extra=g(!0,{},r)),{message:t}}function p(e,r,t){var n=m.guessErrorClass(r.message),o=r.name||n[0],i=n[1],a={exception:{class:o,message:i}};if(e&&(a.exception.description=e||"uncaught exception"),r.stack){var s,u,c,p,f,d,h,w;for(a.frames=[],h=0;h<r.stack.length;++h)s=r.stack[h],u={filename:s.url?v.sanitizeUrl(s.url):"(unknown)",lineno:s.line||null,method:s.func&&"?"!==s.func?s.func:"[anonymous]",colno:s.column},c=p=f=null,d=s.context?s.context.length:0,d&&(w=Math.floor(d/2),p=s.context.slice(0,w),c=s.context[w],f=s.context.slice(w)),c&&(u.code=c),(p||f)&&(u.context={},p&&p.length&&(u.context.pre=p),f&&f.length&&(u.context.post=f)),s.args&&(u.args=s.args),a.frames.push(u);return a.frames.reverse(),t&&(a.extra=g(!0,{},t)),{trace:a}}return l(o+": "+i,t)}function f(){var e;try{for(;e=window._rollbarPayloadQueue.shift();)d(e)}finally{h=void 0}}function d(e){var r=e.endpointUrl,t=e.accessToken,n=e.payload,o=e.callback||function(){},i=(new Date).getTime();i-L>=6e4&&(L=i,R=0);var a=window._globalRollbarOptions.maxItems,c=window._globalRollbarOptions.itemsPerMinute,l=function(){return!n.ignoreRateLimit&&a>=1&&T>=a},p=function(){return!n.ignoreRateLimit&&c>=1&&R>=c};return l()?void o(new Error(a+" max items reached")):p()?void o(new Error(c+" items per minute reached")):(T++,R++,l()&&_._log(_.options.uncaughtErrorLevel,"maxItems has been hit. Ignoring errors for the remainder of the current page load.",null,{maxItems:a},null,!1,!0),n.ignoreRateLimit&&delete n.ignoreRateLimit,void y.post(r,t,n,function(r,t){return r?(r instanceof b&&(e.callback=function(){},setTimeout(function(){u(e)},s.RETRY_DELAY)),o(r)):o(null,t)}))}var h,g=t(4),m=t(5),v=t(8),w=t(11),y=w.XHR,b=w.ConnectionError,E=null;s.NOTIFIER_VERSION="1.9.4",s.DEFAULT_ENDPOINT="api.rollbar.com/api/1/",s.DEFAULT_SCRUB_FIELDS=["pw","pass","passwd","password","secret","confirm_password","confirmPassword","password_confirmation","passwordConfirmation","access_token","accessToken","secret_key","secretKey","secretToken"],s.DEFAULT_LOG_LEVEL="debug",s.DEFAULT_REPORT_LEVEL="debug",s.DEFAULT_UNCAUGHT_ERROR_LEVEL="error",s.DEFAULT_ITEMS_PER_MIN=60,s.DEFAULT_MAX_ITEMS=0,s.LEVELS={debug:0,info:1,warning:2,error:3,critical:4},s.RETRY_DELAY=1e4,window._rollbarPayloadQueue=window._rollbarPayloadQueue||[],window._globalRollbarOptions={startTime:(new Date).getTime(),maxItems:s.DEFAULT_MAX_ITEMS,itemsPerMinute:s.DEFAULT_ITEMS_PER_MIN};var _,x=s.prototype;x._getLogArgs=function(e){for(var r,t,n,i,a,u,c=this.options.logLevel||s.DEFAULT_LOG_LEVEL,l=[],p=0;p<e.length;++p)u=e[p],a=v.typeName(u),"string"===a?r?l.push(u):r=u:"function"===a?i=o(u,this):"date"===a?l.push(u):"error"===a||u instanceof Error||"undefined"!=typeof DOMException&&u instanceof DOMException?t?l.push(u):t=u:"object"!==a&&"array"!==a||(n?l.push(u):n=u);return l.length&&(n=n||{},n.extraArgs=l),{level:c,message:r,err:t,custom:n,callback:i}},x._route=function(e){var r=this.options.endpoint,t=/\/$/.test(r),n=/^\//.test(e);return t&&n?e=e.substring(1):t||n||(e="/"+e),r+e},x._processShimQueue=function(e){for(var r,t,n,o,i,a,u,c={};t=e.shift();)r=t.shim,n=t.method,o=t.args,i=r.parentShim,u=c[r.shimId],u||(i?(a=c[i.shimId],u=new s(a)):u=this,c[r.shimId]=u),u[n]&&v.isType(u[n],"function")&&u[n].apply(u,o)},x._buildPayload=function(e,r,t,n,o){var i=this.options.accessToken,a=this.options.environment,u=g(!0,{},this.options.payload),c=v.uuid4();if(void 0===s.LEVELS[r])throw new Error("Invalid level");if(!t&&!n&&!o)throw new Error("No message, stack info or custom data");var l={environment:a,endpoint:this.options.endpoint,uuid:c,level:r,platform:"browser",framework:"browser-js",language:"javascript",body:this._buildBody(t,n,o),request:{url:window.location.href,query_string:window.location.search,user_ip:"$remote_ip"},client:{runtime_ms:e.getTime()-window._globalRollbarOptions.startTime,timestamp:Math.round(e.getTime()/1e3),javascript:{browser:window.navigator.userAgent,language:window.navigator.language,cookie_enabled:window.navigator.cookieEnabled,screen:{width:window.screen.width,height:window.screen.height},plugins:this._getBrowserPlugins()}},server:{},notifier:{name:"rollbar-browser-js",version:s.NOTIFIER_VERSION}};u.body&&delete u.body;var p={access_token:i,data:g(!0,l,u)};return this._scrub(p.data),p},x._buildBody=function(e,r,t){var n;return n=r?p(e,r,t):l(e,t)},x._getBrowserPlugins=function(){if(!this._browserPlugins){var e,r,t=window.navigator.plugins||[],n=t.length,o=[];for(r=0;r<n;++r)e=t[r],o.push({name:e.name,description:e.description});this._browserPlugins=o}return this._browserPlugins},x._scrub=function(e){function r(e,r,t,n,o,i){return r+v.redact(i)}function t(e){var t;if(v.isType(e,"string"))for(t=0;t<s.length;++t)e=e.replace(s[t],r);return e}function n(e,r){var t;for(t=0;t<a.length;++t)if(a[t].test(e)){r=v.redact(r);break}return r}function o(e,r){var o=n(e,r);return o===r?t(o):o}var i=this.options.scrubFields,a=this._getScrubFieldRegexs(i),s=this._getScrubQueryParamRegexs(i);return v.traverse(e,o),e},x._getScrubFieldRegexs=function(e){for(var r,t=[],n=0;n<e.length;++n)r="\\[?(%5[bB])?"+e[n]+"\\[?(%5[bB])?\\]?(%5[dD])?",t.push(new RegExp(r,"i"));return t},x._getScrubQueryParamRegexs=function(e){for(var r,t=[],n=0;n<e.length;++n)r="\\[?(%5[bB])?"+e[n]+"\\[?(%5[bB])?\\]?(%5[dD])?",t.push(new RegExp("("+r+"=)([^&\\n]+)","igm"));return t},x._urlIsWhitelisted=function(e){var r,t,n,o,i,a,s,u,c,l;try{if(r=this.options.hostWhiteList,t=e&&e.data&&e.data.body&&e.data.body.trace,!r||0===r.length)return!0;if(!t)return!0;for(s=r.length,i=t.frames.length,c=0;c<i;c++){if(n=t.frames[c],o=n.filename,!v.isType(o,"string"))return!0;for(l=0;l<s;l++)if(a=r[l],u=new RegExp(a),u.test(o))return!0}}catch(e){return this.configure({hostWhiteList:null}),v.consoleError("[Rollbar]: Error while reading your configuration's hostWhiteList option. Removing custom hostWhiteList.",e),!0}return!1},x._messageIsIgnored=function(e){var r,t,n,o,i,a,s,u,c;try{if(i=!1,n=this.options.ignoredMessages,!n||0===n.length)return!1;if(s=e&&e.data&&e.data.body,u=s&&s.trace&&s.trace.exception&&s.trace.exception.message,c=s&&s.message&&s.message.body,r=u||c,!r)return!1;for(o=n.length,t=0;t<o&&(a=new RegExp(n[t],"gi"),!(i=a.test(r)));t++);}catch(e){this.configure({ignoredMessages:null}),v.consoleError("[Rollbar]: Error while reading your configuration's ignoredMessages option. Removing custom ignoredMessages.")}return i},x._enqueuePayload=function(e,r,t,n){var o={callback:n,accessToken:this.options.accessToken,endpointUrl:this._route("item/"),payload:e},i=function(){if(n){var e="This item was not sent to Rollbar because it was ignored. This can happen if a custom checkIgnore() function was used or if the item's level was less than the notifier' reportLevel. See https://rollbar.com/docs/notifier/rollbar.js/configuration for more details.";n(null,{err:0,result:{id:null,uuid:null,message:e}})}};if(this._internalCheckIgnore(r,t,e))return void i();try{if(v.isType(this.options.checkIgnore,"function")&&this.options.checkIgnore(r,t,e))return void i()}catch(e){this.configure({checkIgnore:null}),v.consoleError("[Rollbar]: Error while calling custom checkIgnore() function. Removing custom checkIgnore().",e)}if(this._urlIsWhitelisted(e)&&!this._messageIsIgnored(e)){if(this.options.verbose){if(e.data&&e.data.body&&e.data.body.trace){var a=e.data.body.trace,s=a.exception.message;v.consoleError("[Rollbar]: ",s)}v.consoleInfo("[Rollbar]: ",o)}v.isType(this.options.logFunction,"function")&&this.options.logFunction(o);try{v.isType(this.options.transform,"function")&&this.options.transform(e)}catch(e){this.configure({transform:null}),v.consoleError("[Rollbar]: Error while calling custom transform() function. Removing custom transform().",e)}this.options.enabled&&u(o)}},x._internalCheckIgnore=function(e,r,t){var n=r[0],o=s.LEVELS[n]||0,i=s.LEVELS[this.options.reportLevel]||0;if(o<i)return!0;var a=this.options?this.options.plugins:{};if(a&&a.jquery&&a.jquery.ignoreAjaxErrors)try{return!!t.data.body.message.extra.isAjax}catch(e){return!1}return!1},x._log=function(e,r,t,n,o,i,a){var s=null;if(t)try{if(s=t._savedStackTrace?t._savedStackTrace:m.parse(t),t===this.lastError)return;this.lastError=t}catch(e){v.consoleError("[Rollbar]: Error while parsing the error object.",e),r=t.message||t.description||r||String(t),t=null}var u=this._buildPayload(new Date,e,r,s,n);return a&&(u.ignoreRateLimit=!0),this._enqueuePayload(u,!!i,[e,r,t,n],o),{uuid:u.data.uuid}},x.log=c(),x.debug=c("debug"),x.info=c("info"),x.warn=c("warning"),x.warning=c("warning"),x.error=c("error"),x.critical=c("critical"),x.uncaughtError=o(function(e,r,t,n,o,i){if(i=i||null,o&&v.isType(o,"error"))return void this._log(this.options.uncaughtErrorLevel,e,o,i,null,!0);if(r&&v.isType(r,"error"))return void this._log(this.options.uncaughtErrorLevel,e,r,i,null,!0);var a={url:r||"",line:t};a.func=m.guessFunctionName(a.url,a.line),a.context=m.gatherContext(a.url,a.line);var s={mode:"onerror",message:o?String(o):e||"uncaught exception",url:document.location.href,stack:[a],useragent:navigator.userAgent},u=this._buildPayload(new Date,this.options.uncaughtErrorLevel,e,s,i);this._enqueuePayload(u,!0,[this.options.uncaughtErrorLevel,e,r,t,n,o])}),x.unhandledRejection=o(function(e,r){var t,n;if(e?(t=e.message||String(e),n=e._rollbarContext):t="unhandled rejection was null or undefined!",n=n||r._rollbarContext||null,e&&v.isType(e,"error"))return void this._log(this.options.uncaughtErrorLevel,t,e,n,null,!0);var o={url:"",line:0};o.func=m.guessFunctionName(o.url,o.line),o.context=m.gatherContext(o.url,o.line);var i={mode:"unhandledrejection",message:t,url:document.location.href,stack:[o],useragent:navigator.userAgent},a=this._buildPayload(new Date,this.options.uncaughtErrorLevel,t,i,n);this._enqueuePayload(a,!0,[this.options.uncaughtErrorLevel,t,o.url,o.line,0,e,r])}),x.global=o(function(e){e=e||{};var r={startTime:e.startTime,maxItems:e.maxItems,itemsPerMinute:e.itemsPerMinute};g(!0,window._globalRollbarOptions,r),void 0!==e.maxItems&&(T=0),void 0!==e.itemsPerMinute&&(R=0)}),x.configure=o(function(e,r){var t=g(!0,{},e);g(!r,this.options,t),this.global(t)}),x.scope=o(function(e){var r=new s(this);return g(!0,r.options.payload,e),r}),x.wrap=function(e,r){try{var t;if(t=v.isType(r,"function")?r:function(){return r||{}},!v.isType(e,"function"))return e;if(e._isWrap)return e;if(!e._wrapped){e._wrapped=function(){try{return e.apply(this,arguments)}catch(r){throw"string"==typeof r&&(r=new String(r)),r.stack||(r._savedStackTrace=m.parse(r)),r._rollbarContext=t()||{},r._rollbarContext._wrappedSource=e.toString(),window._rollbarWrappedError=r,r}},e._wrapped._isWrap=!0;for(var n in e)e.hasOwnProperty(n)&&(e._wrapped[n]=e[n])}return e._wrapped}catch(r){return e}},x.loadFull=function(){v.consoleError("[Rollbar]: Unexpected Rollbar.loadFull() called on a Notifier instance")},s.processPayloads=function(e){return e?void f():void i()};var L=(new Date).getTime(),T=0,R=0;e.exports={Notifier:s,setupJSON:n,topLevelNotifier:a}},function(e,r){"use strict";var t=Object.prototype.hasOwnProperty,n=Object.prototype.toString,o=function(e){return"function"==typeof Array.isArray?Array.isArray(e):"[object Array]"===n.call(e)},i=function(e){if(!e||"[object Object]"!==n.call(e))return!1;var r=t.call(e,"constructor"),o=e.constructor&&e.constructor.prototype&&t.call(e.constructor.prototype,"isPrototypeOf");if(e.constructor&&!r&&!o)return!1;var i;for(i in e);return"undefined"==typeof i||t.call(e,i)};e.exports=function e(){var r,t,n,a,s,u,c=arguments[0],l=1,p=arguments.length,f=!1;for("boolean"==typeof c?(f=c,c=arguments[1]||{},l=2):("object"!=typeof c&&"function"!=typeof c||null==c)&&(c={});l<p;++l)if(r=arguments[l],null!=r)for(t in r)n=c[t],a=r[t],c!==a&&(f&&a&&(i(a)||(s=o(a)))?(s?(s=!1,u=n&&o(n)?n:[]):u=n&&i(n)?n:{},c[t]=e(f,u,a)):"undefined"!=typeof a&&(c[t]=a));return c}},function(e,r,t){"use strict";function n(){return l}function o(){return null}function i(e){var r={};return r._stackFrame=e,r.url=e.fileName,r.line=e.lineNumber,r.func=e.functionName,r.column=e.columnNumber,r.args=e.args,r.context=o(r.url,r.line),r}function a(e){function r(){var r=[];try{r=c.parse(e)}catch(e){r=[]}for(var t=[],n=0;n<r.length;n++)t.push(new i(r[n]));return t}return{stack:r(),message:e.message,name:e.name}}function s(e){return new a(e)}function u(e){if(!e)return["Unknown error. There was no error message to display.",""];var r=e.match(p),t="(unknown)";return r&&(t=r[r.length-1],e=e.replace((r[r.length-2]||"")+t+":",""),e=e.replace(/(^[\s]+|[\s]+$)/g,"")),[t,e]}var c=t(6),l="?",p=new RegExp("^(([a-zA-Z0-9-_$ ]*): *)?(Uncaught )?([a-zA-Z0-9-_$ ]*): ");e.exports={guessFunctionName:n,guessErrorClass:u,gatherContext:o,parse:s,Stack:a,Frame:i}},function(e,r,t){var n,o,i;!function(a,s){"use strict";o=[t(7)],n=s,i="function"==typeof n?n.apply(r,o):n,!(void 0!==i&&(e.exports=i))}(this,function(e){"use strict";function r(e,r,t){if("function"==typeof Array.prototype.map)return e.map(r,t);for(var n=new Array(e.length),o=0;o<e.length;o++)n[o]=r.call(t,e[o]);return n}function t(e,r,t){if("function"==typeof Array.prototype.filter)return e.filter(r,t);for(var n=[],o=0;o<e.length;o++)r.call(t,e[o])&&n.push(e[o]);return n}var n=/(^|@)\S+\:\d+/,o=/^\s*at .*(\S+\:\d+|\(native\))/m,i=/^(eval@)?(\[native code\])?$/;return{parse:function(e){if("undefined"!=typeof e.stacktrace||"undefined"!=typeof e["opera#sourceloc"])return this.parseOpera(e);if(e.stack&&e.stack.match(o))return this.parseV8OrIE(e);if(e.stack)return this.parseFFOrSafari(e);throw new Error("Cannot parse given Error object")},extractLocation:function(e){if(e.indexOf(":")===-1)return[e];var r=e.replace(/[\(\)\s]/g,"").split(":"),t=r.pop(),n=r[r.length-1];if(!isNaN(parseFloat(n))&&isFinite(n)){var o=r.pop();return[r.join(":"),o,t]}return[r.join(":"),t,void 0]},parseV8OrIE:function(n){var i=t(n.stack.split("\n"),function(e){return!!e.match(o)},this);return r(i,function(r){r.indexOf("(eval ")>-1&&(r=r.replace(/eval code/g,"eval").replace(/(\(eval at [^\()]*)|(\)\,.*$)/g,""));var t=r.replace(/^\s+/,"").replace(/\(eval code/g,"(").split(/\s+/).slice(1),n=this.extractLocation(t.pop()),o=t.join(" ")||void 0,i="eval"===n[0]?void 0:n[0];return new e(o,void 0,i,n[1],n[2],r)},this)},parseFFOrSafari:function(n){var o=t(n.stack.split("\n"),function(e){return!e.match(i)},this);return r(o,function(r){if(r.indexOf(" > eval")>-1&&(r=r.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g,":$1")),r.indexOf("@")===-1&&r.indexOf(":")===-1)return new e(r);var t=r.split("@"),n=this.extractLocation(t.pop()),o=t.shift()||void 0;return new e(o,void 0,n[0],n[1],n[2],r)},this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(r){for(var t=/Line (\d+).*script (?:in )?(\S+)/i,n=r.message.split("\n"),o=[],i=2,a=n.length;i<a;i+=2){var s=t.exec(n[i]);s&&o.push(new e(void 0,void 0,s[2],s[1],void 0,n[i]))}return o},parseOpera10:function(r){for(var t=/Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i,n=r.stacktrace.split("\n"),o=[],i=0,a=n.length;i<a;i+=2){var s=t.exec(n[i]);s&&o.push(new e(s[3]||void 0,void 0,s[2],s[1],void 0,n[i]))}return o},parseOpera11:function(o){var i=t(o.stack.split("\n"),function(e){return!!e.match(n)&&!e.match(/^Error created at/)},this);return r(i,function(r){var t,n=r.split("@"),o=this.extractLocation(n.pop()),i=n.shift()||"",a=i.replace(/<anonymous function(: (\w+))?>/,"$2").replace(/\([^\)]*\)/g,"")||void 0;i.match(/\(([^\)]*)\)/)&&(t=i.replace(/^[^\(]+\(([^\)]*)\)$/,"$1"));var s=void 0===t||"[arguments not available]"===t?void 0:t.split(",");return new e(a,s,o[0],o[1],o[2],r)},this)}}})},function(e,r,t){var n,o,i;!function(t,a){"use strict";o=[],n=a,i="function"==typeof n?n.apply(r,o):n,!(void 0!==i&&(e.exports=i))}(this,function(){"use strict";function e(e){return!isNaN(parseFloat(e))&&isFinite(e)}function r(e,r,t,n,o,i){void 0!==e&&this.setFunctionName(e),void 0!==r&&this.setArgs(r),void 0!==t&&this.setFileName(t),void 0!==n&&this.setLineNumber(n),void 0!==o&&this.setColumnNumber(o),void 0!==i&&this.setSource(i)}return r.prototype={getFunctionName:function(){return this.functionName},setFunctionName:function(e){this.functionName=String(e)},getArgs:function(){return this.args},setArgs:function(e){if("[object Array]"!==Object.prototype.toString.call(e))throw new TypeError("Args must be an Array");this.args=e},getFileName:function(){return this.fileName},setFileName:function(e){this.fileName=String(e)},getLineNumber:function(){return this.lineNumber},setLineNumber:function(r){if(!e(r))throw new TypeError("Line Number must be a Number");this.lineNumber=Number(r)},getColumnNumber:function(){return this.columnNumber},setColumnNumber:function(r){if(!e(r))throw new TypeError("Column Number must be a Number");this.columnNumber=Number(r)},getSource:function(){return this.source},setSource:function(e){this.source=String(e)},toString:function(){var r=this.getFunctionName()||"{anonymous}",t="("+(this.getArgs()||[]).join(",")+")",n=this.getFileName()?"@"+this.getFileName():"",o=e(this.getLineNumber())?":"+this.getLineNumber():"",i=e(this.getColumnNumber())?":"+this.getColumnNumber():"";return r+t+n+o+i}},r})},function(e,r,t){"use strict";function n(e){v=e}function o(e){return{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function i(e,r){return o(e)===r}function a(e){if(!i(e,"string"))throw new Error("received invalid input");for(var r=w,t=r.parser[r.strictMode?"strict":"loose"].exec(e),n={},o=14;o--;)n[r.key[o]]=t[o]||"";return n[r.q.name]={},n[r.key[12]].replace(r.q.parser,function(e,t,o){t&&(n[r.q.name][t]=o)}),n}function s(e){var r=a(e);return""===r.anchor&&(r.source=r.source.replace("#","")),e=r.source.replace("?"+r.query,"")}function u(e,r){var t,n,o,a=i(e,"object"),s=i(e,"array"),c=[];if(a)for(t in e)e.hasOwnProperty(t)&&c.push(t);else if(s)for(o=0;o<e.length;++o)c.push(o);for(o=0;o<c.length;++o)t=c[o],n=e[t],a=i(n,"object"),s=i(n,"array"),a||s?e[t]=u(n,r):e[t]=r(t,n);return e}function c(e){return e=String(e),new Array(e.length+1).join("*")}function l(){var e=(new Date).getTime(),r="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(r){var t=(e+16*Math.random())%16|0;return e=Math.floor(e/16),("x"===r?t:7&t|8).toString(16)});return r}function p(e){return"function"!=typeof Object.create?function(e){var r=function(){};return function(e){if(null!==e&&e!==Object(e))throw TypeError("Argument must be an object, or null");r.prototype=e||{};var t=new r;return r.prototype=null,null===e&&(t.__proto__=null),t}}()(e):Object.create(e)}function f(){for(var e=[],r=0;r<arguments.length;r++){var t=arguments[r];"object"==typeof t?(t=v.stringify(t),t.length>500&&(t=t.substr(0,500)+"...")):"undefined"==typeof t&&(t="undefined"),e.push(t)}return e.join(" ")}function d(){m.ieVersion()<=8?console.error(f.apply(null,arguments)):console.error.apply(null,arguments)}function h(){m.ieVersion()<=8?console.info(f.apply(null,arguments)):console.info.apply(null,arguments)}function g(){m.ieVersion()<=8?console.log(f.apply(null,arguments)):console.log.apply(null,arguments)}t(9);var m=t(10),v=null,w={strictMode:!1,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},y={setupJSON:n,isType:i,parseUri:a,parseUriOptions:w,redact:c,sanitizeUrl:s,traverse:u,typeName:o,uuid4:l,objectCreate:p,consoleError:d,consoleInfo:h,consoleLog:g};e.exports=y},function(e,r){!function(e){"use strict";e.console||(e.console={});for(var r,t,n=e.console,o=function(){},i=["memory"],a="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");r=i.pop();)n[r]||(n[r]={});for(;t=a.pop();)n[t]||(n[t]=o)}("undefined"==typeof window?this:window)},function(e,r){"use strict";function t(){for(var e,r=3,t=document.createElement("div"),n=t.getElementsByTagName("i");t.innerHTML="<!--[if gt IE "+ ++r+"]><i></i><![endif]-->",n[0];);return r>4?r:e}var n={ieVersion:t};e.exports=n},function(e,r,t){"use strict";function n(e){a=e}function o(e){this.name="Connection Error",this.message=e,this.stack=(new Error).stack}var i=t(8),a=null;o.prototype=i.objectCreate(Error.prototype),o.prototype.constructor=o;var s={XMLHttpFactories:[function(){return new XMLHttpRequest},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")}],createXMLHTTPObject:function(){var e,r=!1,t=s.XMLHttpFactories,n=t.length;for(e=0;e<n;e++)try{r=t[e]();break}catch(e){}return r},post:function(e,r,t,n){if(!i.isType(t,"object"))throw new Error("Expected an object to POST");t=a.stringify(t),n=n||function(){};var u=s.createXMLHTTPObject();if(u)try{try{var c=function(){try{if(c&&4===u.readyState){c=void 0;var e=a.parse(u.responseText);200===u.status?n(null,e):i.isType(u.status,"number")&&u.status>=400&&u.status<600?(403==u.status&&i.consoleError("[Rollbar]:"+e.message),n(new Error(String(u.status)))):n(new o("XHR response had no status code (likely connection failure)"))}}catch(e){var r;r=e&&e.stack?e:new Error(e),n(r)}};u.open("POST",e,!0),u.setRequestHeader&&(u.setRequestHeader("Content-Type","application/json"),u.setRequestHeader("X-Rollbar-Access-Token",r)),u.onreadystatechange=c,u.send(t)}catch(r){if("undefined"!=typeof XDomainRequest){"http:"===window.location.href.substring(0,5)&&"https"===e.substring(0,5)&&(e="http"+e.substring(5));var l=function(){n(new o("Request timed out"))},p=function(){n(new Error("Error during request"))},f=function(){n(null,a.parse(u.responseText))};u=new XDomainRequest,u.onprogress=function(){},u.ontimeout=l,u.onerror=p,u.onload=f,u.open("POST",e,!0),u.send(t)}}}catch(e){n(e)}}};e.exports={XHR:s,setupJSON:n,ConnectionError:o}}])});
diff --git a/packages/website/public/pdfs/0x_white_paper.pdf b/packages/website/public/pdfs/0x_white_paper.pdf
new file mode 100644
index 000000000..340e8a73a
--- /dev/null
+++ b/packages/website/public/pdfs/0x_white_paper.pdf
Binary files differ
diff --git a/packages/website/public/videos/0xAnimation.mp4 b/packages/website/public/videos/0xAnimation.mp4
new file mode 100644
index 000000000..d78c07d4f
--- /dev/null
+++ b/packages/website/public/videos/0xAnimation.mp4
Binary files differ
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
new file mode 100644
index 000000000..d891acdb6
--- /dev/null
+++ b/packages/website/ts/blockchain.ts
@@ -0,0 +1,770 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {
+ ZeroEx,
+ ZeroExError,
+ ExchangeContractErrs,
+ ExchangeContractEventArgs,
+ ExchangeEvents,
+ SubscriptionOpts,
+ IndexedFilterValues,
+ DecodedLogEvent,
+ BlockParam,
+ LogFillContractEventArgs,
+ LogCancelContractEventArgs,
+ Token as ZeroExToken,
+ LogWithDecodedArgs,
+ TransactionReceiptWithDecodedLogs,
+ SignedOrder,
+ Order,
+} from '0x.js';
+import BigNumber from 'bignumber.js';
+import Web3 = require('web3');
+import promisify = require('es6-promisify');
+import findVersions = require('find-versions');
+import compareVersions = require('compare-versions');
+import contract = require('truffle-contract');
+import ethUtil = require('ethereumjs-util');
+import ProviderEngine = require('web3-provider-engine');
+import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
+import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted';
+import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed';
+import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider';
+import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider';
+import {ledgerWalletSubproviderFactory} from 'ts/subproviders/ledger_wallet_subprovider_factory';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {
+ BlockchainErrs,
+ Token,
+ SignatureData,
+ Side,
+ ContractResponse,
+ BlockchainCallErrs,
+ ContractInstance,
+ ProviderType,
+ LedgerWalletSubprovider,
+ EtherscanLinkSuffixes,
+ TokenByAddress,
+ TokenStateByAddress,
+} from 'ts/types';
+import {Web3Wrapper} from 'ts/web3_wrapper';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+import * as MintableArtifacts from '../contracts/Mintable.json';
+
+const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730;
+const BLOCK_NUMBER_BACK_TRACK = 50;
+
+export class Blockchain {
+ public networkId: number;
+ public nodeVersion: string;
+ private zeroEx: ZeroEx;
+ private dispatcher: Dispatcher;
+ private web3Wrapper: Web3Wrapper;
+ private exchangeAddress: string;
+ private tokenTransferProxy: ContractInstance;
+ private tokenRegistry: ContractInstance;
+ private userAddress: string;
+ private cachedProvider: Web3.Provider;
+ private ledgerSubProvider: LedgerWalletSubprovider;
+ private zrxPollIntervalId: number;
+ constructor(dispatcher: Dispatcher, isSalePage: boolean = false) {
+ this.dispatcher = dispatcher;
+ this.userAddress = '';
+ this.onPageLoadInitFireAndForgetAsync();
+ }
+ public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) {
+ const isConnected = !_.isUndefined(newNetworkId);
+ if (!isConnected) {
+ this.networkId = newNetworkId;
+ this.dispatcher.encounteredBlockchainError(BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE);
+ this.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ } else if (this.networkId !== newNetworkId) {
+ this.networkId = newNetworkId;
+ this.dispatcher.encounteredBlockchainError('');
+ await this.fetchTokenInformationAsync();
+ await this.rehydrateStoreWithContractEvents();
+ }
+ }
+ public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) {
+ if (this.userAddress !== newUserAddress) {
+ this.userAddress = newUserAddress;
+ await this.fetchTokenInformationAsync();
+ await this.rehydrateStoreWithContractEvents();
+ }
+ }
+ public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) {
+ if (this.nodeVersion !== nodeVersion) {
+ this.nodeVersion = nodeVersion;
+ }
+ }
+ public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ const tokenIfExists = await this.zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress);
+ return !_.isUndefined(tokenIfExists);
+ }
+ public getLedgerDerivationPathIfExists(): string {
+ if (_.isUndefined(this.ledgerSubProvider)) {
+ return undefined;
+ }
+ const path = this.ledgerSubProvider.getPath();
+ return path;
+ }
+ public updateLedgerDerivationPathIfExists(path: string) {
+ if (_.isUndefined(this.ledgerSubProvider)) {
+ return; // noop
+ }
+ this.ledgerSubProvider.setPath(path);
+ }
+ public updateLedgerDerivationIndex(pathIndex: number) {
+ if (_.isUndefined(this.ledgerSubProvider)) {
+ return; // noop
+ }
+ this.ledgerSubProvider.setPathIndex(pathIndex);
+ }
+ public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ // Should actually be Web3.Provider|ProviderEngine union type but it causes issues
+ // later on in the logic.
+ let provider;
+ switch (providerType) {
+ case ProviderType.LEDGER: {
+ const isU2FSupported = await utils.isU2FSupportedAsync();
+ if (!isU2FSupported) {
+ throw new Error('Cannot update providerType to LEDGER without U2F support');
+ }
+
+ // Cache injected provider so that we can switch the user back to it easily
+ this.cachedProvider = this.web3Wrapper.getProviderObj();
+
+ this.dispatcher.updateUserAddress(''); // Clear old userAddress
+
+ provider = new ProviderEngine();
+ this.ledgerSubProvider = ledgerWalletSubproviderFactory(this.getBlockchainNetworkId.bind(this));
+ provider.addProvider(this.ledgerSubProvider);
+ provider.addProvider(new FilterSubprovider());
+ const networkId = configs.isMainnetEnabled ?
+ constants.MAINNET_NETWORK_ID :
+ constants.TESTNET_NETWORK_ID;
+ provider.addProvider(new RedundantRPCSubprovider(
+ constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId],
+ ));
+ provider.start();
+ this.web3Wrapper.destroy();
+ const shouldPollUserAddress = false;
+ this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress);
+ await this.zeroEx.setProviderAsync(provider);
+ await this.postInstantiationOrUpdatingProviderZeroExAsync();
+ break;
+ }
+
+ case ProviderType.INJECTED: {
+ if (_.isUndefined(this.cachedProvider)) {
+ return; // Going from injected to injected, so we noop
+ }
+ provider = this.cachedProvider;
+ const shouldPollUserAddress = true;
+ this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress);
+ await this.zeroEx.setProviderAsync(provider);
+ await this.postInstantiationOrUpdatingProviderZeroExAsync();
+ delete this.ledgerSubProvider;
+ delete this.cachedProvider;
+ break;
+ }
+
+ default:
+ throw utils.spawnSwitchErr('providerType', providerType);
+ }
+
+ await this.fetchTokenInformationAsync();
+ }
+ public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
+ utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TOKEN_ADDRESS_IS_INVALID);
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+
+ const txHash = await this.zeroEx.token.setProxyAllowanceAsync(
+ token.address, this.userAddress, amountInBaseUnits,
+ );
+ await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ const allowance = amountInBaseUnits;
+ this.dispatcher.replaceTokenAllowanceByAddress(token.address, allowance);
+ }
+ public async transferAsync(token: Token, toAddress: string,
+ amountInBaseUnits: BigNumber): Promise<void> {
+ const txHash = await this.zeroEx.token.transferAsync(
+ token.address, this.userAddress, toAddress, amountInBaseUnits,
+ );
+ await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx);
+ this.dispatcher.showFlashMessage(React.createElement(TokenSendCompleted, {
+ etherScanLinkIfExists,
+ token,
+ toAddress,
+ amountInBaseUnits,
+ }));
+ }
+ public portalOrderToSignedOrder(maker: string, taker: string, makerTokenAddress: string,
+ takerTokenAddress: string, makerTokenAmount: BigNumber,
+ takerTokenAmount: BigNumber, makerFee: BigNumber,
+ takerFee: BigNumber, expirationUnixTimestampSec: BigNumber,
+ feeRecipient: string,
+ signatureData: SignatureData, salt: BigNumber): SignedOrder {
+ const ecSignature = signatureData;
+ const exchangeContractAddress = this.getExchangeContractAddressIfExists();
+ taker = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker;
+ const signedOrder = {
+ ecSignature,
+ exchangeContractAddress,
+ expirationUnixTimestampSec,
+ feeRecipient,
+ maker,
+ makerFee,
+ makerTokenAddress,
+ makerTokenAmount,
+ salt,
+ taker,
+ takerFee,
+ takerTokenAddress,
+ takerTokenAmount,
+ };
+ return signedOrder;
+ }
+ public async fillOrderAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber): Promise<BigNumber> {
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const shouldThrowOnInsufficientBalanceOrAllowance = true;
+
+ const txHash = await this.zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, this.userAddress,
+ );
+ const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
+ this.zeroEx.exchange.throwLogErrorsAsErrors(logs);
+ const logFill = _.find(logs, {event: 'LogFill'});
+ const args = logFill.args as any as LogFillContractEventArgs;
+ const filledTakerTokenAmount = args.filledTakerTokenAmount;
+ return filledTakerTokenAmount;
+ }
+ public async cancelOrderAsync(signedOrder: SignedOrder,
+ cancelTakerTokenAmount: BigNumber): Promise<BigNumber> {
+ const txHash = await this.zeroEx.exchange.cancelOrderAsync(
+ signedOrder, cancelTakerTokenAmount,
+ );
+ const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
+ this.zeroEx.exchange.throwLogErrorsAsErrors(logs);
+ const logCancel = _.find(logs, {event: ExchangeEvents.LogCancel});
+ const args = logCancel.args as any as LogCancelContractEventArgs;
+ const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount;
+ return cancelledTakerTokenAmount;
+ }
+ public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> {
+ utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash');
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ const unavailableTakerAmount = await this.zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash);
+ return unavailableTakerAmount;
+ }
+ public getExchangeContractAddressIfExists() {
+ return this.exchangeAddress;
+ }
+ public toHumanReadableErrorMsg(error: ZeroExError|ExchangeContractErrs, takerAddress: string): string {
+ const ZeroExErrorToHumanReadableError: {[error: string]: string} = {
+ [ZeroExError.ContractDoesNotExist]: 'Contract does not exist',
+ [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist',
+ [ZeroExError.UnhandledError]: ' Unhandled error occured',
+ [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available',
+ [ZeroExError.InvalidSignature]: 'Order signature is not valid',
+ [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network',
+ [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction',
+ [ZeroExError.OutOfGas]: 'Transaction ran out of gas',
+ [ZeroExError.NoNetworkId]: 'No network id detected',
+ };
+ const exchangeContractErrorToHumanReadableError: {[error: string]: string} = {
+ [ExchangeContractErrs.OrderFillExpired]: 'This order has expired',
+ [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired',
+ [ExchangeContractErrs.OrderCancelAmountZero]: 'Order cancel amount can\'t be 0',
+ [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]:
+ 'This order has already been completely filled or cancelled',
+ [ExchangeContractErrs.OrderFillAmountZero]: 'Order fill amount can\'t be 0',
+ [ExchangeContractErrs.OrderRemainingFillAmountZero]:
+ 'This order has already been completely filled or cancelled',
+ [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order',
+ [ExchangeContractErrs.InsufficientTakerBalance]:
+ 'Taker no longer has a sufficient balance to complete this order',
+ [ExchangeContractErrs.InsufficientTakerAllowance]:
+ 'Taker no longer has a sufficient allowance to complete this order',
+ [ExchangeContractErrs.InsufficientMakerBalance]:
+ 'Maker no longer has a sufficient balance to complete this order',
+ [ExchangeContractErrs.InsufficientMakerAllowance]:
+ 'Maker no longer has a sufficient allowance to complete this order',
+ [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees',
+ [ExchangeContractErrs.InsufficientTakerFeeAllowance]:
+ 'Taker no longer has a sufficient allowance to pay fees',
+ [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees',
+ [ExchangeContractErrs.InsufficientMakerFeeAllowance]:
+ 'Maker no longer has a sufficient allowance to pay fees',
+ [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]:
+ `This order can only be filled by ${takerAddress}`,
+ [ExchangeContractErrs.InsufficientRemainingFillAmount]:
+ 'Insufficient remaining fill amount',
+ };
+ const humanReadableErrorMsg = exchangeContractErrorToHumanReadableError[error] ||
+ ZeroExErrorToHumanReadableError[error];
+ return humanReadableErrorMsg;
+ }
+ public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ takerAddress: string): Promise<void> {
+ await this.zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress);
+ }
+ public async validateCancelOrderThrowIfInvalidAsync(order: Order,
+ cancelTakerTokenAmount: BigNumber): Promise<void> {
+ await this.zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
+ }
+ public isValidAddress(address: string): boolean {
+ const lowercaseAddress = address.toLowerCase();
+ return this.web3Wrapper.isAddress(lowercaseAddress);
+ }
+ public async pollTokenBalanceAsync(token: Token) {
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address);
+
+ this.zrxPollIntervalId = window.setInterval(async () => {
+ const [balance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address);
+ if (!balance.eq(currBalance)) {
+ this.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ clearInterval(this.zrxPollIntervalId);
+ delete this.zrxPollIntervalId;
+ }
+ }, 5000);
+ }
+ public async signOrderHashAsync(orderHash: string): Promise<SignatureData> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ const makerAddress = this.userAddress;
+ // If makerAddress is undefined, this means they have a web3 instance injected into their browser
+ // but no account addresses associated with it.
+ if (_.isUndefined(makerAddress)) {
+ throw new Error('Tried to send a sign request but user has no associated addresses');
+ }
+ const ecSignature = await this.zeroEx.signOrderHashAsync(orderHash, makerAddress);
+ const signatureData = _.extend({}, ecSignature, {
+ hash: orderHash,
+ });
+ this.dispatcher.updateSignatureData(signatureData);
+ return signatureData;
+ }
+ public async mintTestTokensAsync(token: Token) {
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const mintableContract = await this.instantiateContractIfExistsAsync(MintableArtifacts, token.address);
+ await mintableContract.mint(constants.MINT_AMOUNT, {
+ from: this.userAddress,
+ });
+ const balanceDelta = constants.MINT_AMOUNT;
+ this.dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta);
+ }
+ public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
+ const balance = await this.web3Wrapper.getBalanceInEthAsync(owner);
+ return balance;
+ }
+ public async convertEthToWrappedEthTokensAsync(amount: BigNumber): Promise<void> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const txHash = await this.zeroEx.etherToken.depositAsync(amount, this.userAddress);
+ await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ }
+ public async convertWrappedEthTokensToEthAsync(amount: BigNumber): Promise<void> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const txHash = await this.zeroEx.etherToken.withdrawAsync(amount, this.userAddress);
+ await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
+ }
+ public async doesContractExistAtAddressAsync(address: string) {
+ const doesContractExist = await this.web3Wrapper.doesContractExistAtAddressAsync(address);
+ return doesContractExist;
+ }
+ public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> {
+ const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, tokenAddress);
+ return tokenBalanceAndAllowance;
+ }
+ public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string):
+ Promise<BigNumber[]> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+
+ if (_.isEmpty(ownerAddress)) {
+ const zero = new BigNumber(0);
+ return [zero, zero];
+ }
+ let balance = new BigNumber(0);
+ let allowance = new BigNumber(0);
+ if (this.doesUserAddressExist()) {
+ balance = await this.zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress);
+ allowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress);
+ }
+ return [balance, allowance];
+ }
+ public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) {
+ const tokenStateByAddress: TokenStateByAddress = {};
+ for (const token of tokens) {
+ let balance = new BigNumber(0);
+ let allowance = new BigNumber(0);
+ if (this.doesUserAddressExist()) {
+ [
+ balance,
+ allowance,
+ ] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address);
+ }
+ const tokenState = {
+ balance,
+ allowance,
+ };
+ tokenStateByAddress[token.address] = tokenState;
+ }
+ this.dispatcher.updateTokenStateByAddress(tokenStateByAddress);
+ }
+ public async getUserAccountsAsync() {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ const userAccountsIfExists = await this.zeroEx.getAvailableAddressesAsync();
+ return userAccountsIfExists;
+ }
+ // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which
+ // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to
+ // manually update it. This should only be called by the LedgerConfigDialog.
+ public updateWeb3WrapperPrevUserAddress(newUserAddress: string) {
+ this.web3Wrapper.updatePrevUserAddress(newUserAddress);
+ }
+ public destroy() {
+ clearInterval(this.zrxPollIntervalId);
+ this.web3Wrapper.destroy();
+ this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget
+ }
+ private async showEtherScanLinkAndAwaitTransactionMinedAsync(
+ txHash: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx);
+ this.dispatcher.showFlashMessage(React.createElement(TransactionSubmitted, {
+ etherScanLinkIfExists,
+ }));
+ const receipt = await this.zeroEx.awaitTransactionMinedAsync(txHash);
+ return receipt;
+ }
+ private doesUserAddressExist(): boolean {
+ return this.userAddress !== '';
+ }
+ private async rehydrateStoreWithContractEvents() {
+ // Ensure we are only ever listening to one set of events
+ await this.stopWatchingExchangeLogFillEventsAsync();
+
+ if (!this.doesUserAddressExist()) {
+ return; // short-circuit
+ }
+
+ if (!_.isUndefined(this.zeroEx)) {
+ // Since we do not have an index on the `taker` address and want to show
+ // transactions where an account is either the `maker` or `taker`, we loop
+ // through all fill events, and filter/cache them client-side.
+ const filterIndexObj = {};
+ await this.startListeningForExchangeLogFillEventsAsync(filterIndexObj);
+ }
+ }
+ private async startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ // Fetch historical logs
+ await this.fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues);
+
+ // Start a subscription for new logs
+ const exchangeAddress = this.getExchangeContractAddressIfExists();
+ const subscriptionId = await this.zeroEx.exchange.subscribeAsync(
+ ExchangeEvents.LogFill, indexFilterValues,
+ async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => {
+ if (err) {
+ // Note: it's not entirely clear from the documentation which
+ // errors will be thrown by `watch`. For now, let's log the error
+ // to rollbar and stop watching when one occurs
+ errorReporter.reportAsync(err); // fire and forget
+ this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget
+ return;
+ } else {
+ await this.addFillEventToTradeHistoryAsync(decodedLogEvent);
+ }
+ });
+ }
+ private async fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) {
+ const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this.userAddress, this.networkId);
+ const subscriptionOpts: SubscriptionOpts = {
+ fromBlock,
+ toBlock: 'latest' as BlockParam,
+ };
+ const decodedLogs = await this.zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>(
+ ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues,
+ );
+ for (const decodedLog of decodedLogs) {
+ console.log('decodedLog', decodedLog);
+ await this.addFillEventToTradeHistoryAsync(decodedLog);
+ }
+ }
+ private async addFillEventToTradeHistoryAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) {
+ const args = decodedLog.args as LogFillContractEventArgs;
+ const isUserMakerOrTaker = args.maker === this.userAddress ||
+ args.taker === this.userAddress;
+ if (!isUserMakerOrTaker) {
+ return; // We aren't interested in the fill event
+ }
+ const isBlockPending = _.isNull(decodedLog.blockNumber);
+ if (!isBlockPending) {
+ // Hack: I've observed the behavior where a client won't register certain fill events
+ // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply
+ // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user
+ // would still attempt to re-fetch events from the previous 50 blocks, but won't need to
+ // re-fetch all events in all blocks.
+ // TODO: Debug if this is a race condition, and apply a more precise fix
+ const blockNumberToSet = decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ?
+ 0 :
+ decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK;
+ tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet);
+ }
+ const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash);
+ const fill = {
+ filledTakerTokenAmount: args.filledTakerTokenAmount,
+ filledMakerTokenAmount: args.filledMakerTokenAmount,
+ logIndex: decodedLog.logIndex,
+ maker: args.maker,
+ orderHash: args.orderHash,
+ taker: args.taker,
+ makerToken: args.makerToken,
+ takerToken: args.takerToken,
+ paidMakerFee: args.paidMakerFee,
+ paidTakerFee: args.paidTakerFee,
+ transactionHash: decodedLog.transactionHash,
+ blockTimestamp,
+ };
+ tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill);
+ }
+ private async stopWatchingExchangeLogFillEventsAsync() {
+ this.zeroEx.exchange.unsubscribeAll();
+ }
+ private async getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ const tokenRegistryTokens = await this.zeroEx.tokenRegistry.getTokensAsync();
+
+ const tokenByAddress: TokenByAddress = {};
+ _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => {
+ // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens
+ // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry
+ const iconUrl = constants.iconUrlBySymbol[t.symbol];
+ const token: Token = {
+ iconUrl,
+ address: t.address,
+ name: t.name,
+ symbol: t.symbol,
+ decimals: t.decimals,
+ isTracked: false,
+ isRegistered: true,
+ };
+ tokenByAddress[token.address] = token;
+ });
+ return tokenByAddress;
+ }
+ private async onPageLoadInitFireAndForgetAsync() {
+ await this.onPageLoadAsync(); // wait for page to load
+
+ // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
+ // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot
+ // retrieve it from within the web3Wrapper constructor. This is and should remain the only
+ // call to a web3 instance outside of web3Wrapper in the entire dapp.
+ // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing
+ // Ethereum node, this call will throw. We need to handle this case gracefully
+ const injectedWeb3 = (window as any).web3;
+ let networkId: number;
+ if (!_.isUndefined(injectedWeb3)) {
+ try {
+ networkId = _.parseInt(await promisify(injectedWeb3.version.getNetwork)());
+ } catch (err) {
+ // Ignore error and proceed with networkId undefined
+ }
+ }
+
+ const provider = await this.getProviderAsync(injectedWeb3, networkId);
+ this.zeroEx = new ZeroEx(provider);
+ await this.updateProviderName(injectedWeb3);
+ const shouldPollUserAddress = true;
+ this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, networkId, shouldPollUserAddress);
+ await this.postInstantiationOrUpdatingProviderZeroExAsync();
+ }
+ // This method should always be run after instantiating or updating the provider
+ // of the ZeroEx instance.
+ private async postInstantiationOrUpdatingProviderZeroExAsync() {
+ utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
+ this.exchangeAddress = await this.zeroEx.exchange.getContractAddressAsync();
+ }
+ private updateProviderName(injectedWeb3: Web3) {
+ const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
+ const providerName = doesInjectedWeb3Exist ?
+ this.getNameGivenProvider(injectedWeb3.currentProvider) :
+ constants.PUBLIC_PROVIDER_NAME;
+ this.dispatcher.updateInjectedProviderName(providerName);
+ }
+ // This is only ever called by the LedgerWallet subprovider in order to retrieve
+ // the current networkId without this value going stale.
+ private getBlockchainNetworkId() {
+ return this.networkId;
+ }
+ private async getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) {
+ const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
+ const publicNodeUrlsIfExistsForNetworkId = constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists];
+ const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId);
+
+ let provider;
+ if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
+ // We catch all requests involving a users account and send it to the injectedWeb3
+ // instance. All other requests go to the public hosted node.
+ provider = new ProviderEngine();
+ provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3));
+ provider.addProvider(new FilterSubprovider());
+ provider.addProvider(new RedundantRPCSubprovider(
+ publicNodeUrlsIfExistsForNetworkId,
+ ));
+ provider.start();
+ } else if (doesInjectedWeb3Exist) {
+ // Since no public node for this network, all requests go to injectedWeb3 instance
+ provider = injectedWeb3.currentProvider;
+ } else {
+ // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node
+ // We do this so that users can still browse the 0x Portal DApp even if they do not have web3
+ // injected into their browser.
+ provider = new ProviderEngine();
+ provider.addProvider(new FilterSubprovider());
+ const networkId = configs.isMainnetEnabled ?
+ constants.MAINNET_NETWORK_ID :
+ constants.TESTNET_NETWORK_ID;
+ provider.addProvider(new RedundantRPCSubprovider(
+ constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId],
+ ));
+ provider.start();
+ }
+
+ return provider;
+ }
+ private getNameGivenProvider(provider: Web3.Provider): string {
+ if (!_.isUndefined((provider as any).isMetaMask)) {
+ return constants.METAMASK_PROVIDER_NAME;
+ }
+
+ // HACK: We use the fact that Parity Signer's provider is an instance of their
+ // internal `Web3FrameProvider` class.
+ const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider');
+ if (isParitySigner) {
+ return constants.PARITY_SIGNER_PROVIDER_NAME;
+ }
+
+ return constants.GENERIC_PROVIDER_NAME;
+ }
+ private async fetchTokenInformationAsync() {
+ utils.assert(!_.isUndefined(this.networkId),
+ 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node');
+
+ this.dispatcher.updateBlockchainIsLoaded(false);
+ this.dispatcher.clearTokenByAddress();
+
+ const tokenRegistryTokensByAddress = await this.getTokenRegistryTokensByAddressAsync();
+
+ // HACK: We need to fetch the userAddress here because otherwise we cannot save the
+ // tracked tokens in localStorage under the users address nor fetch the token
+ // balances and allowances and we need to do this in order not to trigger the blockchain
+ // loading dialog to show up twice. First to load the contracts, and second to load the
+ // balances and allowances.
+ this.userAddress = await this.web3Wrapper.getFirstAccountIfExistsAsync();
+ if (!_.isEmpty(this.userAddress)) {
+ this.dispatcher.updateUserAddress(this.userAddress);
+ }
+
+ let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this.userAddress, this.networkId);
+ const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
+ if (_.isUndefined(trackedTokensIfExists)) {
+ trackedTokensIfExists = _.map(configs.defaultTrackedTokenSymbols, symbol => {
+ const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
+ token.isTracked = true;
+ return token;
+ });
+ _.each(trackedTokensIfExists, token => {
+ trackedTokenStorage.addTrackedTokenToUser(this.userAddress, this.networkId, token);
+ });
+ } else {
+ // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
+ _.each(trackedTokensIfExists, trackedToken => {
+ if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) {
+ tokenRegistryTokensByAddress[trackedToken.address].isTracked = true;
+ }
+ });
+ }
+ const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]);
+ this.dispatcher.updateTokenByAddress(allTokens);
+
+ // Get balance/allowance for tracked tokens
+ await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists);
+
+ const mostPopularTradingPairTokens: Token[] = [
+ _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[0]}),
+ _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[1]}),
+ ];
+ this.dispatcher.updateChosenAssetTokenAddress(Side.deposit, mostPopularTradingPairTokens[0].address);
+ this.dispatcher.updateChosenAssetTokenAddress(Side.receive, mostPopularTradingPairTokens[1].address);
+ this.dispatcher.updateBlockchainIsLoaded(true);
+ }
+ private async instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
+ const c = await contract(artifact);
+ const providerObj = this.web3Wrapper.getProviderObj();
+ c.setProvider(providerObj);
+
+ const artifactNetworkConfigs = artifact.networks[this.networkId];
+ let contractAddress;
+ if (!_.isUndefined(address)) {
+ contractAddress = address;
+ } else if (!_.isUndefined(artifactNetworkConfigs)) {
+ contractAddress = artifactNetworkConfigs.address;
+ }
+
+ if (!_.isUndefined(contractAddress)) {
+ const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
+ if (!doesContractExist) {
+ utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`);
+ throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST);
+ }
+ }
+
+ try {
+ const contractInstance = _.isUndefined(address) ?
+ await c.deployed() :
+ await c.at(address);
+ return contractInstance;
+ } catch (err) {
+ const errMsg = `${err}`;
+ utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
+ if (_.includes(errMsg, 'not been deployed to detected network')) {
+ throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST);
+ } else {
+ await errorReporter.reportAsync(err);
+ throw new Error(BlockchainCallErrs.UNHANDLED_ERROR);
+ }
+ }
+ }
+ private async onPageLoadAsync() {
+ if (document.readyState === 'complete') {
+ return; // Already loaded
+ }
+ return new Promise((resolve, reject) => {
+ window.onload = resolve;
+ });
+ }
+}
diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
new file mode 100644
index 000000000..2e12fc889
--- /dev/null
+++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
@@ -0,0 +1,158 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import {colors} from 'material-ui/styles';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {Blockchain} from 'ts/blockchain';
+import {BlockchainErrs} from 'ts/types';
+
+interface BlockchainErrDialogProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ isOpen: boolean;
+ userAddress: string;
+ toggleDialogFn: (isOpen: boolean) => void;
+ networkId: number;
+}
+
+export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProps, undefined> {
+ public render() {
+ const dialogActions = [
+ <FlatButton
+ label="Ok"
+ primary={true}
+ onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)}
+ />,
+ ];
+
+ const hasWalletAddress = this.props.userAddress !== '';
+ return (
+ <Dialog
+ title={this.getTitle(hasWalletAddress)}
+ titleStyle={{fontWeight: 100}}
+ actions={dialogActions}
+ open={this.props.isOpen}
+ contentStyle={{width: 400}}
+ onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)}
+ autoScrollBodyContent={true}
+ >
+ <div className="pt2" style={{color: colors.grey700}}>
+ {this.renderExplanation(hasWalletAddress)}
+ </div>
+ </Dialog>
+ );
+ }
+ private getTitle(hasWalletAddress: boolean) {
+ if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) {
+ return '0x smart contracts not found';
+ } else if (!hasWalletAddress) {
+ return 'Enable wallet communication';
+ } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) {
+ return 'Disconnected from Ethereum network';
+ } else {
+ return 'Unexpected error';
+ }
+ }
+ private renderExplanation(hasWalletAddress: boolean) {
+ if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) {
+ return this.renderContractsNotDeployedExplanation();
+ } else if (!hasWalletAddress) {
+ return this.renderNoWalletFoundExplanation();
+ } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) {
+ return this.renderDisconnectedFromNode();
+ } else {
+ return this.renderUnexpectedErrorExplanation();
+ }
+ }
+ private renderDisconnectedFromNode() {
+ return (
+ <div>
+ You were disconnected from the backing Ethereum node.
+ {' '}If using <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank">
+ Metamask
+ </a> or <a href={constants.MIST_DOWNLOAD_URL} target="_blank">Mist</a> try refreshing
+ {' '}the page. If using a locally hosted Ethereum node, make sure it's still running.
+ </div>
+ );
+ }
+ private renderUnexpectedErrorExplanation() {
+ return (
+ <div>
+ We encountered an unexpected error. Please try refreshing the page.
+ </div>
+ );
+ }
+ private renderNoWalletFoundExplanation() {
+ return (
+ <div>
+ <div>
+ We were unable to access an Ethereum wallet you control. In order to interact
+ {' '}with the 0x portal dApp,
+ we need a way to interact with one of your Ethereum wallets.
+ {' '}There are two easy ways you can enable us to do that:
+ </div>
+ <h4>1. Metamask chrome extension</h4>
+ <div>
+ You can install the{' '}
+ <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank">
+ Metamask
+ </a> Chrome extension Ethereum wallet. Once installed and set up, refresh this page.
+ <div className="pt1">
+ <span className="bold">Note:</span>
+ {' '}If you already have Metamask installed, make sure it is unlocked.
+ </div>
+ </div>
+ <h4>Parity Signer</h4>
+ <div>
+ The <a href={constants.PARITY_CHROME_STORE_URL} target="_blank">Parity Signer
+ Chrome extension</a>{' '}lets you connect to a locally running Parity node.
+ Make sure you have started your local Parity node with{' '}
+ {configs.isMainnetEnabled && '`parity ui` or'} `parity --chain kovan ui`{' '}
+ in order to connect to {configs.isMainnetEnabled ? 'mainnet or Kovan respectively.' : 'Kovan.'}
+ </div>
+ <div className="pt2">
+ <span className="bold">Note:</span>
+ {' '}If you have done one of the above steps and are still seeing this message,
+ {' '}we might still be unable to retrieve an Ethereum address by calling `web3.eth.accounts`.
+ {' '}Make sure you have created at least one Ethereum address.
+ </div>
+ </div>
+ );
+ }
+ private renderContractsNotDeployedExplanation() {
+ return (
+ <div>
+ <div>
+ The 0x smart contracts are not deployed on the Ethereum network you are
+ {' '}currently connected to (network Id: {this.props.networkId}).
+ {' '}In order to use the 0x portal dApp,
+ {' '}please connect to the
+ {' '}{constants.TESTNET_NAME} testnet (network Id: {constants.TESTNET_NETWORK_ID})
+ {configs.isMainnetEnabled ?
+ ` or ${constants.MAINNET_NAME} (network Id: ${constants.MAINNET_NETWORK_ID}).` :
+ `.`
+ }
+ </div>
+ <h4>Metamask</h4>
+ <div>
+ If you are using{' '}
+ <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank">
+ Metamask
+ </a>, you can switch networks in the top left corner of the extension popover.
+ </div>
+ <h4>Parity Signer</h4>
+ <div>
+ If using the <a href={constants.PARITY_CHROME_STORE_URL} target="_blank">Parity Signer
+ Chrome extension</a>, make sure to start your local Parity node with{' '}
+ {configs.isMainnetEnabled ?
+ '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \
+ or Kovan respectively.' :
+ '`parity --chain kovan ui` in order to connect to Kovan.'
+ }
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
new file mode 100644
index 000000000..1db85e375
--- /dev/null
+++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup';
+import RadioButton from 'material-ui/RadioButton';
+import {Side, Token, TokenState} from 'ts/types';
+import {TokenAmountInput} from 'ts/components/inputs/token_amount_input';
+import {EthAmountInput} from 'ts/components/inputs/eth_amount_input';
+import BigNumber from 'bignumber.js';
+
+interface EthWethConversionDialogProps {
+ onComplete: (direction: Side, value: BigNumber) => void;
+ onCancelled: () => void;
+ isOpen: boolean;
+ token: Token;
+ tokenState: TokenState;
+ etherBalance: BigNumber;
+}
+
+interface EthWethConversionDialogState {
+ value?: BigNumber;
+ direction: Side;
+ shouldShowIncompleteErrs: boolean;
+ hasErrors: boolean;
+}
+
+export class EthWethConversionDialog extends
+ React.Component<EthWethConversionDialogProps, EthWethConversionDialogState> {
+ constructor() {
+ super();
+ this.state = {
+ direction: Side.deposit,
+ shouldShowIncompleteErrs: false,
+ hasErrors: true,
+ };
+ }
+ public render() {
+ const convertDialogActions = [
+ <FlatButton
+ key="cancel"
+ label="Cancel"
+ onTouchTap={this.onCancel.bind(this)}
+ />,
+ <FlatButton
+ key="convert"
+ label="Convert"
+ primary={true}
+ onTouchTap={this.onConvertClick.bind(this)}
+ />,
+ ];
+ return (
+ <Dialog
+ title="I want to convert"
+ titleStyle={{fontWeight: 100}}
+ actions={convertDialogActions}
+ open={this.props.isOpen}
+ >
+ {this.renderConversionDialogBody()}
+ </Dialog>
+ );
+ }
+ private renderConversionDialogBody() {
+ return (
+ <div className="mx-auto" style={{maxWidth: 300}}>
+ <RadioButtonGroup
+ className="pb1"
+ defaultSelected={this.state.direction}
+ name="conversionDirection"
+ onChange={this.onConversionDirectionChange.bind(this)}
+ >
+ <RadioButton
+ className="pb1"
+ value={Side.deposit}
+ label="Ether -> Ether Tokens"
+ />
+ <RadioButton
+ value={Side.receive}
+ label="Ether Tokens -> Ether"
+ />
+ </RadioButtonGroup>
+ {this.state.direction === Side.receive ?
+ <TokenAmountInput
+ label="Amount to convert"
+ token={this.props.token}
+ tokenState={this.props.tokenState}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={false}
+ onChange={this.onValueChange.bind(this)}
+ amount={this.state.value}
+ onVisitBalancesPageClick={this.props.onCancelled}
+ /> :
+ <EthAmountInput
+ label="Amount to convert"
+ balance={this.props.etherBalance}
+ amount={this.state.value}
+ onChange={this.onValueChange.bind(this)}
+ shouldCheckBalance={true}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ onVisitBalancesPageClick={this.props.onCancelled}
+ />
+ }
+ </div>
+ );
+ }
+ private onConversionDirectionChange(e: any, direction: Side) {
+ this.setState({
+ value: undefined,
+ shouldShowIncompleteErrs: false,
+ direction,
+ hasErrors: true,
+ });
+ }
+ private onValueChange(isValid: boolean, amount?: BigNumber) {
+ this.setState({
+ value: amount,
+ hasErrors: !isValid,
+ });
+ }
+ private onConvertClick() {
+ if (this.state.hasErrors) {
+ this.setState({
+ shouldShowIncompleteErrs: true,
+ });
+ } else {
+ const value = this.state.value;
+ this.setState({
+ value: undefined,
+ });
+ this.props.onComplete(this.state.direction, value);
+ }
+ }
+ private onCancel() {
+ this.setState({
+ value: undefined,
+ });
+ this.props.onCancelled();
+ }
+}
diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
new file mode 100644
index 000000000..f89935500
--- /dev/null
+++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
@@ -0,0 +1,288 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import {colors} from 'material-ui/styles';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import TextField from 'material-ui/TextField';
+import {
+ Table,
+ TableBody,
+ TableHeader,
+ TableRow,
+ TableHeaderColumn,
+ TableRowColumn,
+} from 'material-ui/Table';
+import ReactTooltip = require('react-tooltip');
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {Blockchain} from 'ts/blockchain';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button';
+
+const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`;
+
+enum LedgerSteps {
+ CONNECT,
+ SELECT_ADDRESS,
+}
+
+interface LedgerConfigDialogProps {
+ isOpen: boolean;
+ toggleDialogFn: (isOpen: boolean) => void;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ networkId: number;
+}
+
+interface LedgerConfigDialogState {
+ didConnectFail: boolean;
+ stepIndex: LedgerSteps;
+ userAddresses: string[];
+ addressBalances: BigNumber[];
+ derivationPath: string;
+ derivationErrMsg: string;
+}
+
+export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> {
+ constructor(props: LedgerConfigDialogProps) {
+ super(props);
+ this.state = {
+ didConnectFail: false,
+ stepIndex: LedgerSteps.CONNECT,
+ userAddresses: [],
+ addressBalances: [],
+ derivationPath: constants.DEFAULT_DERIVATION_PATH,
+ derivationErrMsg: '',
+ };
+ }
+ public render() {
+ const dialogActions = [
+ <FlatButton
+ label="Cancel"
+ onTouchTap={this.onClose.bind(this)}
+ />,
+ ];
+ const dialogTitle = this.state.stepIndex === LedgerSteps.CONNECT ?
+ 'Connect to your Ledger' :
+ 'Select desired address';
+ return (
+ <Dialog
+ title={dialogTitle}
+ titleStyle={{fontWeight: 100}}
+ actions={dialogActions}
+ open={this.props.isOpen}
+ onRequestClose={this.onClose.bind(this)}
+ autoScrollBodyContent={true}
+ bodyStyle={{paddingBottom: 0}}
+ >
+ <div style={{color: colors.grey700, paddingTop: 1}}>
+ {this.state.stepIndex === LedgerSteps.CONNECT &&
+ this.renderConnectStep()
+ }
+ {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS &&
+ this.renderSelectAddressStep()
+ }
+ </div>
+ </Dialog>
+ );
+ }
+ private renderConnectStep() {
+ return (
+ <div>
+ <div className="h4 pt3">
+ Follow these instructions before proceeding:
+ </div>
+ <ol>
+ <li className="pb1">
+ Connect your Ledger Nano S & Open the Ethereum application
+ </li>
+ <li className="pb1">
+ Verify that Browser Support is enabled in Settings
+ </li>
+ <li className="pb1">
+ If no Browser Support is found in settings, verify that you have{' '}
+ <a href="https://www.ledgerwallet.com/apps/manager" target="_blank">Firmware >1.2</a>
+ </li>
+ </ol>
+ <div className="center pb3">
+ <LifeCycleRaisedButton
+ isPrimary={true}
+ labelReady="Connect to Ledger"
+ labelLoading="Connecting..."
+ labelComplete="Connected!"
+ onClickAsyncFn={this.onConnectLedgerClickAsync.bind(this, true)}
+ />
+ {this.state.didConnectFail &&
+ <div className="pt2 left-align" style={{color: colors.red200}}>
+ Failed to connect. Follow the instructions and try again.
+ </div>
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderSelectAddressStep() {
+ return (
+ <div>
+ <div>
+ <Table
+ bodyStyle={{height: 300}}
+ onRowSelection={this.onAddressSelected.bind(this)}
+ >
+ <TableHeader displaySelectAll={false}>
+ <TableRow>
+ <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn>
+ <TableHeaderColumn>Balance</TableHeaderColumn>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {this.renderAddressTableRows()}
+ </TableBody>
+ </Table>
+ </div>
+ <div className="flex pt2" style={{height: 100}}>
+ <div className="overflow-hidden" style={{width: 180}}>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500}}
+ floatingLabelText="Update path derivation (advanced)"
+ value={this.state.derivationPath}
+ errorText={this.state.derivationErrMsg}
+ onChange={this.onDerivationPathChanged.bind(this)}
+ />
+ </div>
+ <div className="pl2" style={{paddingTop: 28}}>
+ <LifeCycleRaisedButton
+ labelReady="Update"
+ labelLoading="Updating..."
+ labelComplete="Updated!"
+ onClickAsyncFn={this.onFetchAddressesForDerivationPathAsync.bind(this, true)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderAddressTableRows() {
+ const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => {
+ const balance = this.state.addressBalances[i];
+ const addressTooltipId = `address-${userAddress}`;
+ const balanceTooltipId = `balance-${userAddress}`;
+ const networkName = constants.networkNameById[this.props.networkId];
+ // We specifically prefix kovan ETH.
+ // TODO: We should probably add prefixes for all networks
+ const isKovanNetwork = networkName === 'Kovan';
+ const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`;
+ return (
+ <TableRow key={userAddress} style={{height: 40}}>
+ <TableRowColumn colSpan={2}>
+ <div
+ data-tip={true}
+ data-for={addressTooltipId}
+ >
+ {userAddress}
+ </div>
+ <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip>
+ </TableRowColumn>
+ <TableRowColumn>
+ <div
+ data-tip={true}
+ data-for={balanceTooltipId}
+ >
+ {balanceString}
+ </div>
+ <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip>
+ </TableRowColumn>
+ </TableRow>
+ );
+ });
+ return rows;
+ }
+ private onClose() {
+ this.setState({
+ didConnectFail: false,
+ });
+ const isOpen = false;
+ this.props.toggleDialogFn(isOpen);
+ }
+ private onAddressSelected(selectedRowIndexes: number[]) {
+ const selectedRowIndex = selectedRowIndexes[0];
+ this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex);
+ const selectedAddress = this.state.userAddresses[selectedRowIndex];
+ const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
+ this.props.dispatcher.updateUserAddress(selectedAddress);
+ this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
+ this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
+ this.setState({
+ stepIndex: LedgerSteps.CONNECT,
+ });
+ const isOpen = false;
+ this.props.toggleDialogFn(isOpen);
+ }
+ private async onFetchAddressesForDerivationPathAsync() {
+ const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists();
+ if (currentlySetPath === this.state.derivationPath) {
+ return;
+ }
+ this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath);
+ const didSucceed = await this.fetchAddressesAndBalancesAsync();
+ if (!didSucceed) {
+ this.setState({
+ derivationErrMsg: 'Failed to connect to Ledger.',
+ });
+ }
+ return didSucceed;
+ }
+ private async fetchAddressesAndBalancesAsync() {
+ let userAddresses: string[];
+ const addressBalances: BigNumber[] = [];
+ try {
+ userAddresses = await this.getUserAddressesAsync();
+ for (const address of userAddresses) {
+ const balance = await this.props.blockchain.getBalanceInEthAsync(address);
+ addressBalances.push(balance);
+ }
+ } catch (err) {
+ utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
+ this.setState({
+ didConnectFail: true,
+ });
+ return false;
+ }
+ this.setState({
+ userAddresses,
+ addressBalances,
+ });
+ return true;
+ }
+ private onDerivationPathChanged(e: any, derivationPath: string) {
+ let derivationErrMsg = '';
+ if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) {
+ derivationErrMsg = 'Must be valid Ethereum path.';
+ }
+
+ this.setState({
+ derivationPath,
+ derivationErrMsg,
+ });
+ }
+ private async onConnectLedgerClickAsync() {
+ const didSucceed = await this.fetchAddressesAndBalancesAsync();
+ if (didSucceed) {
+ this.setState({
+ stepIndex: LedgerSteps.SELECT_ADDRESS,
+ });
+ }
+ return didSucceed;
+ }
+ private async getUserAddressesAsync(): Promise<string[]> {
+ let userAddresses: string[];
+ userAddresses = await this.props.blockchain.getUserAccountsAsync();
+
+ if (_.isEmpty(userAddresses)) {
+ throw new Error('No addresses retrieved.');
+ }
+ return userAddresses;
+ }
+}
diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx
new file mode 100644
index 000000000..8f870b42f
--- /dev/null
+++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx
@@ -0,0 +1,44 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import FlatButton from 'material-ui/FlatButton';
+import Dialog from 'material-ui/Dialog';
+import {constants} from 'ts/utils/constants';
+
+interface PortalDisclaimerDialogProps {
+ isOpen: boolean;
+ onToggleDialog: () => void;
+}
+
+export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) {
+ return (
+ <Dialog
+ title="0x Portal Disclaimer"
+ titleStyle={{fontWeight: 100}}
+ actions={[
+ <FlatButton
+ label="I Agree"
+ onTouchTap={props.onToggleDialog.bind(this)}
+ />,
+ ]}
+ open={props.isOpen}
+ onRequestClose={props.onToggleDialog.bind(this)}
+ autoScrollBodyContent={true}
+ modal={true}
+ >
+ <div className="pt2" style={{color: colors.grey700}}>
+ <div>
+ 0x Portal is a free software-based tool intended to help users to
+ buy and sell ERC20-compatible blockchain tokens through the 0x protocol
+ on a purely peer-to-peer basis. 0x portal is not a regulated marketplace,
+ exchange or intermediary of any kind, and therefore, you should only use
+ 0x portal to exchange tokens that are not securities, commodity interests,
+ or any other form of regulated instrument. 0x has not attempted to screen
+ or otherwise limit the tokens that you may enter in 0x Portal. By clicking
+ “I Agree” below, you understand that you are solely responsible for using 0x
+ Portal and buying and selling tokens using 0x Portal in compliance with all
+ applicable laws and regulations.
+ </div>
+ </div>
+ </Dialog>
+ );
+}
diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx
new file mode 100644
index 000000000..10417a326
--- /dev/null
+++ b/packages/website/ts/components/dialogs/send_dialog.tsx
@@ -0,0 +1,126 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup';
+import RadioButton from 'material-ui/RadioButton';
+import {Side, Token, TokenState} from 'ts/types';
+import {TokenAmountInput} from 'ts/components/inputs/token_amount_input';
+import {EthAmountInput} from 'ts/components/inputs/eth_amount_input';
+import {AddressInput} from 'ts/components/inputs/address_input';
+import BigNumber from 'bignumber.js';
+
+interface SendDialogProps {
+ onComplete: (recipient: string, value: BigNumber) => void;
+ onCancelled: () => void;
+ isOpen: boolean;
+ token: Token;
+ tokenState: TokenState;
+}
+
+interface SendDialogState {
+ value?: BigNumber;
+ recipient: string;
+ shouldShowIncompleteErrs: boolean;
+ isAmountValid: boolean;
+}
+
+export class SendDialog extends React.Component<SendDialogProps, SendDialogState> {
+ constructor() {
+ super();
+ this.state = {
+ recipient: '',
+ shouldShowIncompleteErrs: false,
+ isAmountValid: false,
+ };
+ }
+ public render() {
+ const transferDialogActions = [
+ <FlatButton
+ key="cancelTransfer"
+ label="Cancel"
+ onTouchTap={this.onCancel.bind(this)}
+ />,
+ <FlatButton
+ key="sendTransfer"
+ disabled={this.hasErrors()}
+ label="Send"
+ primary={true}
+ onTouchTap={this.onSendClick.bind(this)}
+ />,
+ ];
+ return (
+ <Dialog
+ title="I want to send"
+ titleStyle={{fontWeight: 100}}
+ actions={transferDialogActions}
+ open={this.props.isOpen}
+ >
+ {this.renderSendDialogBody()}
+ </Dialog>
+ );
+ }
+ private renderSendDialogBody() {
+ return (
+ <div className="mx-auto" style={{maxWidth: 300}}>
+ <div style={{height: 80}}>
+ <AddressInput
+ initialAddress={this.state.recipient}
+ updateAddress={this.onRecipientChange.bind(this)}
+ isRequired={true}
+ label={'Recipient address'}
+ hintText={'Address'}
+ />
+ </div>
+ <TokenAmountInput
+ label="Amount to send"
+ token={this.props.token}
+ tokenState={this.props.tokenState}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={false}
+ onChange={this.onValueChange.bind(this)}
+ amount={this.state.value}
+ onVisitBalancesPageClick={this.props.onCancelled}
+ />
+ </div>
+ );
+ }
+ private onRecipientChange(recipient?: string) {
+ this.setState({
+ shouldShowIncompleteErrs: false,
+ recipient,
+ });
+ }
+ private onValueChange(isValid: boolean, amount?: BigNumber) {
+ this.setState({
+ isAmountValid: isValid,
+ value: amount,
+ });
+ }
+ private onSendClick() {
+ if (this.hasErrors()) {
+ this.setState({
+ shouldShowIncompleteErrs: true,
+ });
+ } else {
+ const value = this.state.value;
+ this.setState({
+ recipient: undefined,
+ value: undefined,
+ });
+ this.props.onComplete(this.state.recipient, value);
+ }
+ }
+ private onCancel() {
+ this.setState({
+ value: undefined,
+ });
+ this.props.onCancelled();
+ }
+ private hasErrors() {
+ return _.isUndefined(this.state.recipient) ||
+ _.isUndefined(this.state.value) ||
+ !this.state.isAmountValid;
+ }
+}
diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
new file mode 100644
index 000000000..97c654656
--- /dev/null
+++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
@@ -0,0 +1,99 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import FlatButton from 'material-ui/FlatButton';
+import Dialog from 'material-ui/Dialog';
+import {constants} from 'ts/utils/constants';
+import {Blockchain} from 'ts/blockchain';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+import {Token, TokenByAddress} from 'ts/types';
+
+interface TrackTokenConfirmationDialogProps {
+ tokens: Token[];
+ tokenByAddress: TokenByAddress;
+ isOpen: boolean;
+ onToggleDialog: (didConfirmTokenTracking: boolean) => void;
+ dispatcher: Dispatcher;
+ networkId: number;
+ blockchain: Blockchain;
+ userAddress: string;
+}
+
+interface TrackTokenConfirmationDialogState {
+ isAddingTokenToTracked: boolean;
+}
+
+export class TrackTokenConfirmationDialog extends
+ React.Component<TrackTokenConfirmationDialogProps, TrackTokenConfirmationDialogState> {
+ constructor(props: TrackTokenConfirmationDialogProps) {
+ super(props);
+ this.state = {
+ isAddingTokenToTracked: false,
+ };
+ }
+ public render() {
+ const tokens = this.props.tokens;
+ return (
+ <Dialog
+ title="Tracking confirmation"
+ titleStyle={{fontWeight: 100}}
+ actions={[
+ <FlatButton
+ label="No"
+ onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, false)}
+ />,
+ <FlatButton
+ label="Yes"
+ onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, true)}
+ />,
+ ]}
+ open={this.props.isOpen}
+ onRequestClose={this.props.onToggleDialog.bind(this, false)}
+ autoScrollBodyContent={true}
+ >
+ <div className="pt2">
+ <TrackTokenConfirmation
+ tokens={tokens}
+ networkId={this.props.networkId}
+ tokenByAddress={this.props.tokenByAddress}
+ isAddingTokenToTracked={this.state.isAddingTokenToTracked}
+ />
+ </div>
+ </Dialog>
+ );
+ }
+ private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
+ if (!didUserAcceptTracking) {
+ this.props.onToggleDialog(didUserAcceptTracking);
+ return;
+ }
+ this.setState({
+ isAddingTokenToTracked: true,
+ });
+ for (const token of this.props.tokens) {
+ const newTokenEntry = _.assign({}, token);
+
+ newTokenEntry.isTracked = true;
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
+ this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
+
+ const [
+ balance,
+ allowance,
+ ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address);
+ this.props.dispatcher.updateTokenStateByAddress({
+ [token.address]: {
+ balance,
+ allowance,
+ },
+ });
+ }
+
+ this.setState({
+ isAddingTokenToTracked: false,
+ });
+ this.props.onToggleDialog(didUserAcceptTracking);
+ }
+}
diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx
new file mode 100644
index 000000000..28c24cdbe
--- /dev/null
+++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import FlatButton from 'material-ui/FlatButton';
+import Dialog from 'material-ui/Dialog';
+import {constants} from 'ts/utils/constants';
+
+interface U2fNotSupportedDialogProps {
+ isOpen: boolean;
+ onToggleDialog: () => void;
+}
+
+export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) {
+ return (
+ <Dialog
+ title="U2F Not Supported"
+ titleStyle={{fontWeight: 100}}
+ actions={[
+ <FlatButton
+ label="Ok"
+ onTouchTap={props.onToggleDialog.bind(this)}
+ />,
+ ]}
+ open={props.isOpen}
+ onRequestClose={props.onToggleDialog.bind(this)}
+ autoScrollBodyContent={true}
+ >
+ <div className="pt2" style={{color: colors.grey700}}>
+ <div>
+ It looks like your browser does not support U2F connections
+ required for us to communicate with your hardware wallet.
+ Please use a browser that supports U2F connections and try
+ again.
+ </div>
+ <div>
+ <ul>
+ <li className="pb1">Chrome version 38 or later</li>
+ <li className="pb1">Opera version 40 of later</li>
+ <li>
+ Firefox with{' '}
+ <a
+ href={constants.FIREFOX_U2F_ADDON}
+ target="_blank"
+ style={{textDecoration: 'underline'}}
+ >
+ this extension
+ </a>.
+ </li>
+ </ul>
+ </div>
+ </div>
+ </Dialog>
+ );
+}
diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx
new file mode 100644
index 000000000..fd8b713f4
--- /dev/null
+++ b/packages/website/ts/components/eth_weth_conversion_button.tsx
@@ -0,0 +1,101 @@
+import * as _ from 'lodash';
+import {ZeroEx} from '0x.js';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import RaisedButton from 'material-ui/RaisedButton';
+import {BlockchainCallErrs, TokenState} from 'ts/types';
+import {EthWethConversionDialog} from 'ts/components/dialogs/eth_weth_conversion_dialog';
+import {Side, Token} from 'ts/types';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {Blockchain} from 'ts/blockchain';
+
+interface EthWethConversionButtonProps {
+ ethToken: Token;
+ ethTokenState: TokenState;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ userEtherBalance: BigNumber;
+ onError: () => void;
+}
+
+interface EthWethConversionButtonState {
+ isEthConversionDialogVisible: boolean;
+ isEthConversionHappening: boolean;
+}
+
+export class EthWethConversionButton extends
+ React.Component<EthWethConversionButtonProps, EthWethConversionButtonState> {
+ public constructor(props: EthWethConversionButtonProps) {
+ super(props);
+ this.state = {
+ isEthConversionDialogVisible: false,
+ isEthConversionHappening: false,
+ };
+ }
+ public render() {
+ const labelStyle = this.state.isEthConversionHappening ? {fontSize: 10} : {};
+ return (
+ <div>
+ <RaisedButton
+ style={{width: '100%'}}
+ labelStyle={labelStyle}
+ disabled={this.state.isEthConversionHappening}
+ label={this.state.isEthConversionHappening ? 'Converting...' : 'Convert'}
+ onClick={this.toggleConversionDialog.bind(this)}
+ />
+ <EthWethConversionDialog
+ isOpen={this.state.isEthConversionDialogVisible}
+ onComplete={this.onConversionAmountSelectedAsync.bind(this)}
+ onCancelled={this.toggleConversionDialog.bind(this)}
+ etherBalance={this.props.userEtherBalance}
+ token={this.props.ethToken}
+ tokenState={this.props.ethTokenState}
+ />
+ </div>
+ );
+ }
+ private toggleConversionDialog() {
+ this.setState({
+ isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible,
+ });
+ }
+ private async onConversionAmountSelectedAsync(direction: Side, value: BigNumber) {
+ this.setState({
+ isEthConversionHappening: true,
+ });
+ this.toggleConversionDialog();
+ const token = this.props.ethToken;
+ const tokenState = this.props.ethTokenState;
+ let balance = tokenState.balance;
+ try {
+ if (direction === Side.deposit) {
+ await this.props.blockchain.convertEthToWrappedEthTokensAsync(value);
+ const ethAmount = ZeroEx.toUnitAmount(value, constants.ETH_DECIMAL_PLACES);
+ this.props.dispatcher.showFlashMessage(`Successfully converted ${ethAmount.toString()} ETH to WETH`);
+ balance = balance.plus(value);
+ } else {
+ await this.props.blockchain.convertWrappedEthTokensToEthAsync(value);
+ const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
+ this.props.dispatcher.showFlashMessage(`Successfully converted ${tokenAmount.toString()} WETH to ETH`);
+ balance = balance.minus(value);
+ }
+ this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ } catch (err) {
+ const errMsg = '' + err;
+ if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ } else if (!_.includes(errMsg, 'User denied transaction')) {
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ this.props.onError();
+ }
+ }
+ this.setState({
+ isEthConversionHappening: false,
+ });
+ }
+}
diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx
new file mode 100644
index 000000000..dc965283e
--- /dev/null
+++ b/packages/website/ts/components/fill_order.tsx
@@ -0,0 +1,714 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as accounting from 'accounting';
+import {Link} from 'react-router-dom';
+import {ZeroEx, Order as ZeroExOrder} from '0x.js';
+import * as moment from 'moment';
+import BigNumber from 'bignumber.js';
+import Paper from 'material-ui/Paper';
+import {Card, CardText, CardHeader} from 'material-ui/Card';
+import Divider from 'material-ui/Divider';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {
+ Side,
+ TokenByAddress,
+ TokenStateByAddress,
+ Order,
+ BlockchainErrs,
+ OrderToken,
+ Token,
+ ExchangeContractErrs,
+ AlertTypes,
+ ContractResponse,
+ WebsitePaths,
+} from 'ts/types';
+import {Alert} from 'ts/components/ui/alert';
+import {Identicon} from 'ts/components/ui/identicon';
+import {EthereumAddress} from 'ts/components/ui/ethereum_address';
+import {TokenAmountInput} from 'ts/components/inputs/token_amount_input';
+import {FillWarningDialog} from 'ts/components/fill_warning_dialog';
+import {FillOrderJSON} from 'ts/components/fill_order_json';
+import {VisualOrder} from 'ts/components/visual_order';
+import {SchemaValidator} from 'ts/schemas/validator';
+import {orderSchema} from 'ts/schemas/order_schema';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {Blockchain} from 'ts/blockchain';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+import {TrackTokenConfirmationDialog} from 'ts/components/dialogs/track_token_confirmation_dialog';
+
+const CUSTOM_LIGHT_GRAY = '#BBBBBB';
+
+interface FillOrderProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ orderFillAmount: BigNumber;
+ isOrderInUrl: boolean;
+ networkId: number;
+ userAddress: string;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ initialOrder: Order;
+ dispatcher: Dispatcher;
+}
+
+interface FillOrderState {
+ didOrderValidationRun: boolean;
+ areAllInvolvedTokensTracked: boolean;
+ globalErrMsg: string;
+ orderJSON: string;
+ orderJSONErrMsg: string;
+ parsedOrder: Order;
+ didFillOrderSucceed: boolean;
+ didCancelOrderSucceed: boolean;
+ unavailableTakerAmount: BigNumber;
+ isMakerTokenAddressInRegistry: boolean;
+ isTakerTokenAddressInRegistry: boolean;
+ isFillWarningDialogOpen: boolean;
+ isFilling: boolean;
+ isCancelling: boolean;
+ isConfirmingTokenTracking: boolean;
+ tokensToTrack: Token[];
+}
+
+export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
+ private validator: SchemaValidator;
+ constructor(props: FillOrderProps) {
+ super(props);
+ this.state = {
+ globalErrMsg: '',
+ didOrderValidationRun: false,
+ areAllInvolvedTokensTracked: false,
+ didFillOrderSucceed: false,
+ didCancelOrderSucceed: false,
+ orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder),
+ orderJSONErrMsg: '',
+ parsedOrder: this.props.initialOrder,
+ unavailableTakerAmount: new BigNumber(0),
+ isMakerTokenAddressInRegistry: false,
+ isTakerTokenAddressInRegistry: false,
+ isFillWarningDialogOpen: false,
+ isFilling: false,
+ isCancelling: false,
+ isConfirmingTokenTracking: false,
+ tokensToTrack: [],
+ };
+ this.validator = new SchemaValidator();
+ }
+ public componentWillMount() {
+ if (!_.isEmpty(this.state.orderJSON)) {
+ this.validateFillOrderFireAndForgetAsync(this.state.orderJSON);
+ }
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div className="clearfix lg-px4 md-px4 sm-px2" style={{minHeight: 600}}>
+ <h3>Fill an order</h3>
+ <Divider />
+ <div>
+ {!this.props.isOrderInUrl &&
+ <div>
+ <div className="pt2 pb2">
+ Paste an order JSON snippet below to begin
+ </div>
+ <div className="pb2">Order JSON</div>
+ <FillOrderJSON
+ blockchain={this.props.blockchain}
+ tokenByAddress={this.props.tokenByAddress}
+ networkId={this.props.networkId}
+ orderJSON={this.state.orderJSON}
+ onFillOrderJSONChanged={this.onFillOrderJSONChanged.bind(this)}
+ />
+ {this.renderOrderJsonNotices()}
+ </div>
+ }
+ <div>
+ {!_.isUndefined(this.state.parsedOrder) && this.state.didOrderValidationRun
+ && this.state.areAllInvolvedTokensTracked &&
+ this.renderVisualOrder()
+ }
+ </div>
+ {this.props.isOrderInUrl &&
+ <div className="pt2">
+ <Card style={{boxShadow: 'none', backgroundColor: 'none', border: '1px solid #eceaea'}}>
+ <CardHeader
+ title="Order JSON"
+ actAsExpander={true}
+ showExpandableButton={true}
+ />
+ <CardText expandable={true}>
+ <FillOrderJSON
+ blockchain={this.props.blockchain}
+ tokenByAddress={this.props.tokenByAddress}
+ networkId={this.props.networkId}
+ orderJSON={this.state.orderJSON}
+ onFillOrderJSONChanged={this.onFillOrderJSONChanged.bind(this)}
+ />
+ </CardText>
+ </Card>
+ {this.renderOrderJsonNotices()}
+ </div>
+ }
+ </div>
+ <FillWarningDialog
+ isOpen={this.state.isFillWarningDialogOpen}
+ onToggleDialog={this.onFillWarningClosed.bind(this)}
+ />
+ <TrackTokenConfirmationDialog
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ tokenByAddress={this.props.tokenByAddress}
+ dispatcher={this.props.dispatcher}
+ tokens={this.state.tokensToTrack}
+ isOpen={this.state.isConfirmingTokenTracking}
+ onToggleDialog={this.onToggleTrackConfirmDialog.bind(this)}
+ />
+ </div>
+ );
+ }
+ private renderOrderJsonNotices() {
+ return (
+ <div>
+ {!_.isUndefined(this.props.initialOrder) && !this.state.didOrderValidationRun &&
+ <div className="pt2">
+ <span className="pr1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ <span>Validating order...</span>
+ </div>
+ }
+ {!_.isEmpty(this.state.orderJSONErrMsg) &&
+ <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} />
+ }
+ </div>
+ );
+ }
+ private renderVisualOrder() {
+ const takerTokenAddress = this.state.parsedOrder.taker.token.address;
+ const takerToken = this.props.tokenByAddress[takerTokenAddress];
+ const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount);
+ const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount);
+ const takerAssetToken = {
+ amount: orderTakerAmount.minus(this.state.unavailableTakerAmount),
+ symbol: takerToken.symbol,
+ };
+ const fillToken = this.props.tokenByAddress[takerToken.address];
+ const fillTokenState = this.props.tokenStateByAddress[takerToken.address];
+ const makerTokenAddress = this.state.parsedOrder.maker.token.address;
+ const makerToken = this.props.tokenByAddress[makerTokenAddress];
+ const makerAssetToken = {
+ amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount),
+ symbol: makerToken.symbol,
+ };
+ const fillAssetToken = {
+ amount: this.props.orderFillAmount,
+ symbol: takerToken.symbol,
+ };
+ const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) ? this.state.parsedOrder.taker.address :
+ this.props.userAddress;
+ const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration);
+ const exchangeRate = orderMakerAmount.div(orderTakerAmount);
+
+ let orderReceiveAmount = 0;
+ if (!_.isUndefined(this.props.orderFillAmount)) {
+ const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount);
+ orderReceiveAmount = this.formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals);
+ }
+ const isUserMaker = !_.isUndefined(this.state.parsedOrder) &&
+ this.state.parsedOrder.maker.address === this.props.userAddress;
+ const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration);
+ return (
+ <div className="pt3 pb1">
+ <div className="clearfix pb2" style={{width: '100%'}}>
+ <div className="inline left">Order details</div>
+ <div className="inline right" style={{minWidth: 208}}>
+ <div className="col col-4 pl2" style={{color: '#BEBEBE'}}>
+ Maker:
+ </div>
+ <div className="col col-2 pr1">
+ <Identicon
+ address={this.state.parsedOrder.maker.address}
+ diameter={23}
+ />
+ </div>
+ <div className="col col-6">
+ <EthereumAddress
+ address={this.state.parsedOrder.maker.address}
+ networkId={this.props.networkId}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="lg-px4 md-px4 sm-px0">
+ <div className="lg-px4 md-px4 sm-px1 pt1">
+ <VisualOrder
+ orderTakerAddress={orderTaker}
+ orderMakerAddress={this.state.parsedOrder.maker.address}
+ makerAssetToken={makerAssetToken}
+ takerAssetToken={takerAssetToken}
+ tokenByAddress={this.props.tokenByAddress}
+ makerToken={makerToken}
+ takerToken={takerToken}
+ networkId={this.props.networkId}
+ isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry}
+ isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry}
+ />
+ <div className="center pt3 pb2">
+ Expires: {expiryDate} UTC
+ </div>
+ </div>
+ </div>
+ {!isUserMaker &&
+ <div className="clearfix mx-auto" style={{width: 315, height: 108}}>
+ <div className="col col-7" style={{maxWidth: 235}}>
+ <TokenAmountInput
+ label="Fill amount"
+ onChange={this.onFillAmountChange.bind(this)}
+ shouldShowIncompleteErrs={false}
+ token={fillToken}
+ tokenState={fillTokenState}
+ amount={fillAssetToken.amount}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={true}
+ />
+ </div>
+ <div
+ className="col col-5 pl1"
+ style={{color: CUSTOM_LIGHT_GRAY, paddingTop: 39}}
+ >
+ = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol}
+ </div>
+ </div>
+ }
+ <div>
+ {isUserMaker ?
+ <div>
+ <RaisedButton
+ style={{width: '100%'}}
+ disabled={this.state.isCancelling}
+ label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'}
+ onClick={this.onCancelOrderClickFireAndForgetAsync.bind(this)}
+ />
+ {this.state.didCancelOrderSucceed &&
+ <Alert
+ type={AlertTypes.SUCCESS}
+ message={this.renderCancelSuccessMsg()}
+ />
+ }
+ </div> :
+ <div>
+ <RaisedButton
+ style={{width: '100%'}}
+ disabled={this.state.isFilling}
+ label={this.state.isFilling ? 'Filling order...' : 'Fill order'}
+ onClick={this.onFillOrderClick.bind(this)}
+ />
+ {!_.isEmpty(this.state.globalErrMsg) &&
+ <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
+ }
+ {this.state.didFillOrderSucceed &&
+ <Alert
+ type={AlertTypes.SUCCESS}
+ message={this.renderFillSuccessMsg()}
+ />
+ }
+ </div>
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderFillSuccessMsg() {
+ return (
+ <div>
+ Order successfully filled. See the trade details in your{' '}
+ <Link
+ to={`${WebsitePaths.Portal}/trades`}
+ style={{color: 'white'}}
+ >
+ trade history
+ </Link>
+ </div>
+ );
+ }
+ private renderCancelSuccessMsg() {
+ return (
+ <div>
+ Order successfully cancelled.
+ </div>
+ );
+ }
+ private onFillOrderClick() {
+ if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) {
+ this.setState({
+ isFillWarningDialogOpen: true,
+ });
+ } else {
+ this.onFillOrderClickFireAndForgetAsync();
+ }
+ }
+ private onFillWarningClosed(didUserCancel: boolean) {
+ this.setState({
+ isFillWarningDialogOpen: false,
+ });
+ if (!didUserCancel) {
+ this.onFillOrderClickFireAndForgetAsync();
+ }
+ }
+ private onFillAmountChange(isValid: boolean, amount?: BigNumber) {
+ this.props.dispatcher.updateOrderFillAmount(amount);
+ }
+ private onFillOrderJSONChanged(event: any) {
+ const orderJSON = event.target.value;
+ this.setState({
+ didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg),
+ didFillOrderSucceed: false,
+ });
+ this.validateFillOrderFireAndForgetAsync(orderJSON);
+ }
+ private async checkForUntrackedTokensAndAskToAdd() {
+ if (!_.isEmpty(this.state.orderJSONErrMsg)) {
+ return;
+ }
+
+ const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address];
+ const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address];
+
+ const tokensToTrack = [];
+ const isUnseenMakerToken = _.isUndefined(makerTokenIfExists);
+ const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked;
+ if (isUnseenMakerToken) {
+ tokensToTrack.push(_.assign({}, this.state.parsedOrder.maker.token, {
+ iconUrl: undefined,
+ isTracked: false,
+ isRegistered: false,
+ }));
+ } else if (!isMakerTokenTracked) {
+ tokensToTrack.push(makerTokenIfExists);
+ }
+ const isUnseenTakerToken = _.isUndefined(takerTokenIfExists);
+ const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked;
+ if (isUnseenTakerToken) {
+ tokensToTrack.push(_.assign({}, this.state.parsedOrder.taker.token, {
+ iconUrl: undefined,
+ isTracked: false,
+ isRegistered: false,
+ }));
+ } else if (!isTakerTokenTracked) {
+ tokensToTrack.push(takerTokenIfExists);
+ }
+ if (!_.isEmpty(tokensToTrack)) {
+ this.setState({
+ isConfirmingTokenTracking: true,
+ tokensToTrack,
+ });
+ } else {
+ this.setState({
+ areAllInvolvedTokensTracked: true,
+ });
+ }
+ }
+ private async validateFillOrderFireAndForgetAsync(orderJSON: string) {
+ let orderJSONErrMsg = '';
+ let parsedOrder: Order;
+ try {
+ const order = JSON.parse(orderJSON);
+ const validationResult = this.validator.validate(order, orderSchema);
+ if (validationResult.errors.length > 0) {
+ orderJSONErrMsg = 'Submitted order JSON is not a valid order';
+ utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`);
+ return;
+ }
+ parsedOrder = order;
+
+ const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
+ const makerAmount = new BigNumber(parsedOrder.maker.amount);
+ const takerAmount = new BigNumber(parsedOrder.taker.amount);
+ const expiration = new BigNumber(parsedOrder.expiration);
+ const salt = new BigNumber(parsedOrder.salt);
+ const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount);
+ const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount);
+
+ const zeroExOrder: ZeroExOrder = {
+ exchangeContractAddress: parsedOrder.exchangeContract,
+ expirationUnixTimestampSec: expiration,
+ feeRecipient: parsedOrder.feeRecipient,
+ maker: parsedOrder.maker.address,
+ makerFee: parsedMakerFee,
+ makerTokenAddress: parsedOrder.maker.token.address,
+ makerTokenAmount: makerAmount,
+ salt,
+ taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address,
+ takerFee: parsedTakerFee,
+ takerTokenAddress: parsedOrder.taker.token.address,
+ takerTokenAmount: takerAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
+
+ const signature = parsedOrder.signature;
+ const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address);
+ if (this.props.networkId !== parsedOrder.networkId) {
+ orderJSONErrMsg = `This order was made on another Ethereum network
+ (id: ${parsedOrder.networkId}). Connect to this network to fill.`;
+ parsedOrder = undefined;
+ } else if (exchangeContractAddr !== parsedOrder.exchangeContract) {
+ orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.';
+ parsedOrder = undefined;
+ } else if (orderHash !== signature.hash) {
+ orderJSONErrMsg = 'Order hash does not match supplied plaintext values';
+ parsedOrder = undefined;
+ } else if (!isValidSignature) {
+ orderJSONErrMsg = 'Order signature is invalid';
+ parsedOrder = undefined;
+ } else {
+ // Update user supplied order cache so that if they navigate away from fill view
+ // e.g to set a token allowance, when they come back, the fill order persists
+ this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder);
+ }
+ } catch (err) {
+ utils.consoleLog(`Validate order err: ${err}`);
+ if (!_.isEmpty(orderJSON)) {
+ orderJSONErrMsg = 'Submitted order JSON is not valid JSON';
+ }
+ this.setState({
+ didOrderValidationRun: true,
+ orderJSON,
+ orderJSONErrMsg,
+ parsedOrder,
+ });
+ return;
+ }
+
+ let unavailableTakerAmount = new BigNumber(0);
+ if (!_.isEmpty(orderJSONErrMsg)) {
+ // Clear cache entry if user updates orderJSON to invalid entry
+ this.props.dispatcher.updateUserSuppliedOrderCache(undefined);
+ } else {
+ const orderHash = parsedOrder.signature.hash;
+ unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
+ const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
+ parsedOrder.maker.token.address,
+ );
+ const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
+ parsedOrder.taker.token.address,
+ );
+ this.setState({
+ isMakerTokenAddressInRegistry,
+ isTakerTokenAddressInRegistry,
+ });
+ }
+
+ this.setState({
+ didOrderValidationRun: true,
+ orderJSON,
+ orderJSONErrMsg,
+ parsedOrder,
+ unavailableTakerAmount,
+ });
+
+ await this.checkForUntrackedTokensAndAskToAdd();
+ }
+ private async onFillOrderClickFireAndForgetAsync(): Promise<void> {
+ if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
+
+ this.setState({
+ isFilling: true,
+ didFillOrderSucceed: false,
+ });
+
+ const parsedOrder = this.state.parsedOrder;
+ const orderHash = parsedOrder.signature.hash;
+ const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
+ const takerFillAmount = this.props.orderFillAmount;
+
+ if (_.isUndefined(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ isFilling: false,
+ });
+ return;
+ }
+ let globalErrMsg = '';
+
+ if (_.isUndefined(takerFillAmount)) {
+ globalErrMsg = 'You must specify a fill amount';
+ }
+
+ const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
+ parsedOrder.maker.address,
+ parsedOrder.taker.address,
+ parsedOrder.maker.token.address,
+ parsedOrder.taker.token.address,
+ new BigNumber(parsedOrder.maker.amount),
+ new BigNumber(parsedOrder.taker.amount),
+ new BigNumber(parsedOrder.maker.feeAmount),
+ new BigNumber(parsedOrder.taker.feeAmount),
+ new BigNumber(this.state.parsedOrder.expiration),
+ parsedOrder.feeRecipient,
+ parsedOrder.signature,
+ new BigNumber(parsedOrder.salt),
+ );
+ if (_.isEmpty(globalErrMsg)) {
+ try {
+ await this.props.blockchain.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, takerFillAmount, this.props.userAddress);
+ } catch (err) {
+ globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address);
+ }
+ }
+ if (!_.isEmpty(globalErrMsg)) {
+ this.setState({
+ isFilling: false,
+ globalErrMsg,
+ });
+ return;
+ }
+ try {
+ const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync(
+ signedOrder, this.props.orderFillAmount,
+ );
+ // After fill completes, let's update the token balances
+ const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address];
+ const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address];
+ const tokens = [makerToken, takerToken];
+ await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
+ this.setState({
+ isFilling: false,
+ didFillOrderSucceed: true,
+ globalErrMsg: '',
+ unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount),
+ });
+ return;
+ } catch (err) {
+ this.setState({
+ isFilling: false,
+ });
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'User denied transaction signature')) {
+ return;
+ }
+ globalErrMsg = 'Failed to fill order, please refresh and try again';
+ utils.consoleLog(`${err}`);
+ await errorReporter.reportAsync(err);
+ this.setState({
+ globalErrMsg,
+ });
+ return;
+ }
+ }
+ private async onCancelOrderClickFireAndForgetAsync(): Promise<void> {
+ if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
+
+ this.setState({
+ isCancelling: true,
+ didCancelOrderSucceed: false,
+ });
+
+ const parsedOrder = this.state.parsedOrder;
+ const orderHash = parsedOrder.signature.hash;
+ const takerAddress = this.props.userAddress;
+
+ if (_.isUndefined(takerAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ isFilling: false,
+ });
+ return;
+ }
+ let globalErrMsg = '';
+
+ const takerTokenAmount = new BigNumber(parsedOrder.taker.amount);
+
+ const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
+ parsedOrder.maker.address,
+ parsedOrder.taker.address,
+ parsedOrder.maker.token.address,
+ parsedOrder.taker.token.address,
+ new BigNumber(parsedOrder.maker.amount),
+ takerTokenAmount,
+ new BigNumber(parsedOrder.maker.feeAmount),
+ new BigNumber(parsedOrder.taker.feeAmount),
+ new BigNumber(this.state.parsedOrder.expiration),
+ parsedOrder.feeRecipient,
+ parsedOrder.signature,
+ new BigNumber(parsedOrder.salt),
+ );
+ const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
+ const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount);
+ try {
+ await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(
+ signedOrder, availableTakerTokenAmount);
+ } catch (err) {
+ globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address);
+ }
+ if (!_.isEmpty(globalErrMsg)) {
+ this.setState({
+ isCancelling: false,
+ globalErrMsg,
+ });
+ return;
+ }
+ try {
+ await this.props.blockchain.cancelOrderAsync(
+ signedOrder, availableTakerTokenAmount,
+ );
+ this.setState({
+ isCancelling: false,
+ didCancelOrderSucceed: true,
+ globalErrMsg: '',
+ unavailableTakerAmount: takerTokenAmount,
+ });
+ return;
+ } catch (err) {
+ this.setState({
+ isCancelling: false,
+ });
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'User denied transaction signature')) {
+ return;
+ }
+ globalErrMsg = 'Failed to cancel order, please refresh and try again';
+ utils.consoleLog(`${err}`);
+ await errorReporter.reportAsync(err);
+ this.setState({
+ globalErrMsg,
+ });
+ return;
+ }
+ }
+ private formatCurrencyAmount(amount: BigNumber, decimals: number): number {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000;
+ return roundedUnitAmount;
+ }
+ private onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) {
+ if (!didConfirmTokenTracking) {
+ this.setState({
+ orderJSON: '',
+ orderJSONErrMsg: '',
+ parsedOrder: undefined,
+ });
+ } else {
+ this.setState({
+ areAllInvolvedTokensTracked: true,
+ });
+ }
+ this.setState({
+ isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking,
+ tokensToTrack: [],
+ });
+ }
+}
diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx
new file mode 100644
index 000000000..b355d910b
--- /dev/null
+++ b/packages/website/ts/components/fill_order_json.tsx
@@ -0,0 +1,69 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import {ZeroEx} from '0x.js';
+import Paper from 'material-ui/Paper';
+import TextField from 'material-ui/TextField';
+import {Side, TokenByAddress} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {Blockchain} from 'ts/blockchain';
+import {constants} from 'ts/utils/constants';
+
+interface FillOrderJSONProps {
+ blockchain: Blockchain;
+ tokenByAddress: TokenByAddress;
+ networkId: number;
+ orderJSON: string;
+ onFillOrderJSONChanged: (event: any) => void;
+}
+
+interface FillOrderJSONState {}
+
+export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrderJSONState> {
+ public render() {
+ const tokenAddresses = _.keys(this.props.tokenByAddress);
+ const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists();
+ const hintSideToAssetToken = {
+ [Side.deposit]: {
+ amount: new BigNumber(35),
+ address: tokenAddresses[0],
+ },
+ [Side.receive]: {
+ amount: new BigNumber(89),
+ address: tokenAddresses[1],
+ },
+ };
+ const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
+ const hintSignatureData = {
+ hash: '0xf965a9978a0381ab58f5a2408ad967c...',
+ r: '0xf01103f759e2289a28593eaf22e5820032...',
+ s: '937862111edcba395f8a9e0cc1b2c5e12320...',
+ v: 27,
+ };
+ const hintSalt = ZeroEx.generatePseudoRandomSalt();
+ const hintOrder = utils.generateOrder(this.props.networkId, exchangeContract, hintSideToAssetToken,
+ hintOrderExpiryTimestamp, '', '', constants.MAKER_FEE,
+ constants.TAKER_FEE, constants.FEE_RECIPIENT_ADDRESS,
+ hintSignatureData, this.props.tokenByAddress, hintSalt);
+ const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`;
+ return (
+ <div>
+ <Paper className="p1 overflow-hidden" style={{height: 164}}>
+ <TextField
+ id="orderJSON"
+ hintStyle={{bottom: 0, top: 0}}
+ fullWidth={true}
+ value={this.props.orderJSON}
+ onChange={this.props.onFillOrderJSONChanged.bind(this)}
+ hintText={hintOrderJSON}
+ multiLine={true}
+ rows={6}
+ rowsMax={6}
+ underlineStyle={{display: 'none'}}
+ textareaStyle={{marginTop: 0}}
+ />
+ </Paper>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx
new file mode 100644
index 000000000..029fa8b0c
--- /dev/null
+++ b/packages/website/ts/components/fill_warning_dialog.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import FlatButton from 'material-ui/FlatButton';
+import Dialog from 'material-ui/Dialog';
+
+interface FillWarningDialogProps {
+ isOpen: boolean;
+ onToggleDialog: () => void;
+}
+
+export function FillWarningDialog(props: FillWarningDialogProps) {
+ const didCancel = true;
+ return (
+ <Dialog
+ title="Warning"
+ titleStyle={{fontWeight: 100, color: colors.red500}}
+ actions={[
+ <FlatButton
+ label="Cancel"
+ onTouchTap={props.onToggleDialog.bind(this, didCancel)}
+ />,
+ <FlatButton
+ label="Fill Order"
+ onTouchTap={props.onToggleDialog.bind(this, !didCancel)}
+ />,
+ ]}
+ open={props.isOpen}
+ onRequestClose={props.onToggleDialog.bind(this)}
+ autoScrollBodyContent={true}
+ modal={true}
+ >
+ <div className="pt2" style={{color: colors.grey700}}>
+ <div>
+ At least one of the tokens in this order was not found in the
+ token registry smart contract and may be counterfeit. It is your
+ responsibility to verify the token addresses on Etherscan (
+ <a
+ href="https://0xproject.com/wiki#Verifying-Custom-Tokens"
+ target="_blank"
+ >
+ See this how-to guide
+ </a>) before filling an order. <b>This action may result in the loss of funds</b>.
+ </div>
+ </div>
+ </Dialog>
+ );
+}
diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx
new file mode 100644
index 000000000..c4977d70b
--- /dev/null
+++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+import BigNumber from 'bignumber.js';
+import {ZeroEx} from '0x.js';
+import {Token} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+
+interface TokenSendCompletedProps {
+ etherScanLinkIfExists?: string;
+ token: Token;
+ toAddress: string;
+ amountInBaseUnits: BigNumber;
+}
+
+interface TokenSendCompletedState {}
+
+export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> {
+ public render() {
+ const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) &&
+ (
+ <a
+ style={{color: 'white'}}
+ href={`${this.props.etherScanLinkIfExists}`}
+ target="_blank"
+ >
+ Verify on Etherscan
+ </a>
+ );
+ const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals);
+ const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress);
+ return (
+ <div>
+ {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `}
+ {etherScanLink}
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx
new file mode 100644
index 000000000..7a3cc6e86
--- /dev/null
+++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+
+interface TransactionSubmittedProps {
+ etherScanLinkIfExists?: string;
+}
+
+interface TransactionSubmittedState {}
+
+export class TransactionSubmitted extends React.Component<TransactionSubmittedProps, TransactionSubmittedState> {
+ public render() {
+ if (_.isUndefined(this.props.etherScanLinkIfExists)) {
+ return <div>Transaction submitted to the network</div>;
+ } else {
+ return (
+ <div>
+ Transaction submitted to the network:{' '}
+ <a
+ style={{color: 'white'}}
+ href={`${this.props.etherScanLinkIfExists}`}
+ target="_blank"
+ >
+ Verify on Etherscan
+ </a>
+ </div>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx
new file mode 100644
index 000000000..99f97292e
--- /dev/null
+++ b/packages/website/ts/components/footer.tsx
@@ -0,0 +1,255 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {HashLink} from 'react-router-hash-link';
+import {Styles, WebsitePaths} from 'ts/types';
+import {
+ Link,
+} from 'react-router-dom';
+import {
+ Link as ScrollLink,
+} from 'react-scroll';
+import {constants} from 'ts/utils/constants';
+
+interface MenuItemsBySection {
+ [sectionName: string]: FooterMenuItem[];
+}
+
+interface FooterMenuItem {
+ title: string;
+ path?: string;
+ isExternal?: boolean;
+ fileName?: string;
+}
+
+enum Sections {
+ Documentation = 'Documentation',
+ Community = 'Community',
+ Organization = 'Organization',
+}
+
+const ICON_DIMENSION = 16;
+const CUSTOM_DARK_GRAY = '#393939';
+const CUSTOM_LIGHT_GRAY = '#CACACA';
+const CUSTOM_LIGHTEST_GRAY = '#9E9E9E';
+const menuItemsBySection: MenuItemsBySection = {
+ Documentation: [
+ {
+ title: '0x.js',
+ path: WebsitePaths.ZeroExJs,
+ },
+ {
+ title: '0x Smart Contracts',
+ path: WebsitePaths.SmartContracts,
+ },
+ {
+ title: 'Whitepaper',
+ path: WebsitePaths.Whitepaper,
+ isExternal: true,
+ },
+ {
+ title: 'Wiki',
+ path: WebsitePaths.Wiki,
+ },
+ {
+ title: 'FAQ',
+ path: WebsitePaths.FAQ,
+ },
+ ],
+ Community: [
+ {
+ title: 'Rocket.chat',
+ isExternal: true,
+ path: constants.ZEROEX_CHAT_URL,
+ fileName: 'rocketchat.png',
+ },
+ {
+ title: 'Blog',
+ isExternal: true,
+ path: constants.BLOG_URL,
+ fileName: 'medium.png',
+ },
+ {
+ title: 'Twitter',
+ isExternal: true,
+ path: constants.TWITTER_URL,
+ fileName: 'twitter.png',
+ },
+ {
+ title: 'Reddit',
+ isExternal: true,
+ path: constants.REDDIT_URL,
+ fileName: 'reddit.png',
+ },
+ ],
+ Organization: [
+ {
+ title: 'About',
+ isExternal: false,
+ path: WebsitePaths.About,
+ },
+ {
+ title: 'Careers',
+ isExternal: true,
+ path: constants.ANGELLIST_URL,
+ },
+ {
+ title: 'Contact',
+ isExternal: true,
+ path: 'mailto:team@0xproject.com',
+ },
+ ],
+};
+const linkStyle = {
+ color: 'white',
+ cursor: 'pointer',
+};
+
+const titleToIcon: {[title: string]: string} = {
+ 'Rocket.chat': 'rocketchat.png',
+ 'Blog': 'medium.png',
+ 'Twitter': 'twitter.png',
+ 'Reddit': 'reddit.png',
+};
+
+export interface FooterProps {
+ location: Location;
+}
+
+interface FooterState {}
+
+export class Footer extends React.Component<FooterProps, FooterState> {
+ public render() {
+ return (
+ <div className="relative pb4 pt2" style={{backgroundColor: CUSTOM_DARK_GRAY}}>
+ <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{color: 'white'}}>
+ <div className="col lg-col-4 md-col-4 col-12 left">
+ <div className="sm-mx-auto" style={{width: 148}}>
+ <div>
+ <img src="/images/protocol_logo_white.png" height="30" />
+ </div>
+ <div style={{fontSize: 11, color: CUSTOM_LIGHTEST_GRAY, paddingLeft: 37, paddingTop: 2}}>
+ © ZeroEx, Intl.
+ </div>
+ </div>
+ </div>
+ <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4">
+ <div className="col lg-col-4 md-col-4 col-12">
+ <div className="lg-right md-right sm-center">
+ {this.renderHeader(Sections.Documentation)}
+ {_.map(menuItemsBySection[Sections.Documentation], this.renderMenuItem.bind(this))}
+ </div>
+ </div>
+ <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2">
+ <div className="lg-right md-right sm-center">
+ {this.renderHeader(Sections.Community)}
+ {_.map(menuItemsBySection[Sections.Community], this.renderMenuItem.bind(this))}
+ </div>
+ </div>
+ <div className="col lg-col-4 md-col-4 col-12">
+ <div className="lg-right md-right sm-center">
+ {this.renderHeader(Sections.Organization)}
+ {_.map(menuItemsBySection[Sections.Organization], this.renderMenuItem.bind(this))}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderIcon(fileName: string) {
+ return (
+ <div style={{height: ICON_DIMENSION, width: ICON_DIMENSION}}>
+ <img src={`/images/social/${fileName}`} style={{width: ICON_DIMENSION}} />
+ </div>
+ );
+ }
+ private renderMenuItem(item: FooterMenuItem) {
+ const iconIfExists = titleToIcon[item.title];
+ return (
+ <div
+ key={item.title}
+ className="sm-center"
+ style={{fontSize: 13, paddingTop: 25}}
+ >
+ {item.isExternal ?
+ <a
+ className="text-decoration-none"
+ style={linkStyle}
+ target="_blank"
+ href={item.path}
+ >
+ {!_.isUndefined(iconIfExists) ?
+ <div className="sm-mx-auto" style={{width: 65}}>
+ <div className="flex">
+ <div className="pr1">
+ {this.renderIcon(iconIfExists)}
+ </div>
+ <div>{item.title}</div>
+ </div>
+ </div> :
+ item.title
+ }
+ </a> :
+ <Link
+ to={item.path}
+ style={linkStyle}
+ className="text-decoration-none"
+ >
+ <div>
+ {!_.isUndefined(iconIfExists) &&
+ <div className="pr1">
+ {this.renderIcon(iconIfExists)}
+ </div>
+ }
+ {item.title}
+ </div>
+ </Link>
+ }
+ </div>
+ );
+ }
+ private renderHeader(title: string) {
+ const headerStyle = {
+ textTransform: 'uppercase',
+ color: CUSTOM_LIGHT_GRAY,
+ letterSpacing: 2,
+ fontFamily: 'Roboto Mono',
+ fontSize: 13,
+ };
+ return (
+ <div
+ className="lg-pb2 md-pb2 sm-pt4"
+ style={headerStyle}
+ >
+ {title}
+ </div>
+ );
+ }
+ private renderHomepageLink(title: string) {
+ const hash = title.toLowerCase();
+ if (this.props.location.pathname === WebsitePaths.Home) {
+ return (
+ <ScrollLink
+ style={linkStyle}
+ to={hash}
+ smooth={true}
+ offset={0}
+ duration={constants.HOME_SCROLL_DURATION_MS}
+ containerId="home"
+ >
+ {title}
+ </ScrollLink>
+ );
+ } else {
+ return (
+ <HashLink
+ to={`/#${hash}`}
+ className="text-decoration-none"
+ style={linkStyle}
+ >
+ {title}
+ </HashLink>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx
new file mode 100644
index 000000000..59826d06e
--- /dev/null
+++ b/packages/website/ts/components/generate_order/asset_picker.tsx
@@ -0,0 +1,291 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import Dialog from 'material-ui/Dialog';
+import GridList from 'material-ui/GridList/GridList';
+import GridTile from 'material-ui/GridList/GridTile';
+import FlatButton from 'material-ui/FlatButton';
+import {utils} from 'ts/utils/utils';
+import {Blockchain} from 'ts/blockchain';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ Token,
+ AssetToken,
+ TokenByAddress,
+ Styles,
+ TokenState,
+ DialogConfigs,
+ TokenVisibility,
+} from 'ts/types';
+import {NewTokenForm} from 'ts/components/generate_order/new_token_form';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation';
+import {TokenIcon} from 'ts/components/ui/token_icon';
+
+const TOKEN_ICON_DIMENSION = 100;
+const TILE_DIMENSION = 146;
+enum AssetViews {
+ ASSET_PICKER = 'ASSET_PICKER',
+ NEW_TOKEN_FORM = 'NEW_TOKEN_FORM',
+ CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN',
+}
+
+interface AssetPickerProps {
+ userAddress: string;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ networkId: number;
+ isOpen: boolean;
+ currentTokenAddress: string;
+ onTokenChosen: (tokenAddress: string) => void;
+ tokenByAddress: TokenByAddress;
+ tokenVisibility?: TokenVisibility;
+}
+
+interface AssetPickerState {
+ assetView: AssetViews;
+ hoveredAddress: string | undefined;
+ chosenTrackTokenAddress: string;
+ isAddingTokenToTracked: boolean;
+}
+
+export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerState> {
+ public static defaultProps: Partial<AssetPickerProps> = {
+ tokenVisibility: TokenVisibility.ALL,
+ };
+ private dialogConfigsByAssetView: {[assetView: string]: DialogConfigs};
+ constructor(props: AssetPickerProps) {
+ super(props);
+ this.state = {
+ assetView: AssetViews.ASSET_PICKER,
+ hoveredAddress: undefined,
+ chosenTrackTokenAddress: undefined,
+ isAddingTokenToTracked: false,
+ };
+ this.dialogConfigsByAssetView = {
+ [AssetViews.ASSET_PICKER]: {
+ title: 'Select token',
+ isModal: false,
+ actions: [],
+ },
+ [AssetViews.NEW_TOKEN_FORM]: {
+ title: 'Add an ERC20 token',
+ isModal: false,
+ actions: [],
+ },
+ [AssetViews.CONFIRM_TRACK_TOKEN]: {
+ title: 'Tracking confirmation',
+ isModal: true,
+ actions: [
+ <FlatButton
+ key="noTracking"
+ label="No"
+ onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, false)}
+ />,
+ <FlatButton
+ key="yesTrack"
+ label="Yes"
+ onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, true)}
+ />,
+ ],
+ },
+ };
+ }
+ public render() {
+ const dialogConfigs: DialogConfigs = this.dialogConfigsByAssetView[this.state.assetView];
+ return (
+ <Dialog
+ title={dialogConfigs.title}
+ titleStyle={{fontWeight: 100}}
+ modal={dialogConfigs.isModal}
+ open={this.props.isOpen}
+ actions={dialogConfigs.actions}
+ onRequestClose={this.onCloseDialog.bind(this)}
+ >
+ {this.state.assetView === AssetViews.ASSET_PICKER &&
+ this.renderAssetPicker()
+ }
+ {this.state.assetView === AssetViews.NEW_TOKEN_FORM &&
+ <NewTokenForm
+ blockchain={this.props.blockchain}
+ onNewTokenSubmitted={this.onNewTokenSubmitted.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ }
+ {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN &&
+ this.renderConfirmTrackToken()
+ }
+ </Dialog>
+ );
+ }
+ private renderConfirmTrackToken() {
+ const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress];
+ return (
+ <TrackTokenConfirmation
+ tokens={[token]}
+ tokenByAddress={this.props.tokenByAddress}
+ networkId={this.props.networkId}
+ isAddingTokenToTracked={this.state.isAddingTokenToTracked}
+ />
+ );
+ }
+ private renderAssetPicker() {
+ return (
+ <div
+ className="clearfix flex flex-wrap"
+ style={{overflowY: 'auto', maxWidth: 720, maxHeight: 356, marginBottom: 10}}
+ >
+ {this.renderGridTiles()}
+ </div>
+ );
+ }
+ private renderGridTiles() {
+ let isHovered;
+ let tileStyles;
+ const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => {
+ if ((this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) ||
+ (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)) {
+ return null; // Skip
+ }
+ isHovered = this.state.hoveredAddress === address;
+ tileStyles = {
+ cursor: 'pointer',
+ opacity: isHovered ? 0.6 : 1,
+ };
+ return (
+ <div
+ key={address}
+ style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}}
+ className="p2 mx-auto"
+ onClick={this.onChooseToken.bind(this, address)}
+ onMouseEnter={this.onToggleHover.bind(this, address, true)}
+ onMouseLeave={this.onToggleHover.bind(this, address, false)}
+ >
+ <div className="p1 center">
+ <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
+ </div>
+ <div className="center">{token.name}</div>
+ </div>
+ );
+ });
+ const otherTokenKey = 'otherToken';
+ isHovered = this.state.hoveredAddress === otherTokenKey;
+ tileStyles = {
+ cursor: 'pointer',
+ opacity: isHovered ? 0.6 : 1,
+ };
+ if (this.props.tokenVisibility !== TokenVisibility.TRACKED) {
+ gridTiles.push((
+ <div
+ key={otherTokenKey}
+ style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}}
+ className="p2 mx-auto"
+ onClick={this.onCustomAssetChosen.bind(this)}
+ onMouseEnter={this.onToggleHover.bind(this, otherTokenKey, true)}
+ onMouseLeave={this.onToggleHover.bind(this, otherTokenKey, false)}
+ >
+ <div className="p1 center">
+ <i
+ style={{fontSize: 105, paddingLeft: 1, paddingRight: 1}}
+ className="zmdi zmdi-plus-circle"
+ />
+ </div>
+ <div className="center">Other ERC20 Token</div>
+ </div>
+ ));
+ }
+ return gridTiles;
+ }
+ private onToggleHover(address: string, isHovered: boolean) {
+ const hoveredAddress = isHovered ? address : undefined;
+ this.setState({
+ hoveredAddress,
+ });
+ }
+ private onCloseDialog() {
+ this.setState({
+ assetView: AssetViews.ASSET_PICKER,
+ });
+ this.props.onTokenChosen(this.props.currentTokenAddress);
+ }
+ private onChooseToken(tokenAddress: string) {
+ const token = this.props.tokenByAddress[tokenAddress];
+ if (token.isTracked) {
+ this.props.onTokenChosen(tokenAddress);
+ } else {
+ this.setState({
+ assetView: AssetViews.CONFIRM_TRACK_TOKEN,
+ chosenTrackTokenAddress: tokenAddress,
+ });
+ }
+ }
+ private getTitle() {
+ switch (this.state.assetView) {
+ case AssetViews.ASSET_PICKER:
+ return 'Select token';
+
+ case AssetViews.NEW_TOKEN_FORM:
+ return 'Add an ERC20 token';
+
+ case AssetViews.CONFIRM_TRACK_TOKEN:
+ return 'Tracking confirmation';
+
+ default:
+ throw utils.spawnSwitchErr('assetView', this.state.assetView);
+ }
+ }
+ private onCustomAssetChosen() {
+ this.setState({
+ assetView: AssetViews.NEW_TOKEN_FORM,
+ });
+ }
+ private onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
+ this.props.dispatcher.updateTokenStateByAddress({
+ [newToken.address]: newTokenState,
+ });
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
+ this.props.dispatcher.addTokenToTokenByAddress(newToken);
+ this.setState({
+ assetView: AssetViews.ASSET_PICKER,
+ });
+ this.props.onTokenChosen(newToken.address);
+ }
+ private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
+ if (!didUserAcceptTracking) {
+ this.setState({
+ isAddingTokenToTracked: false,
+ assetView: AssetViews.ASSET_PICKER,
+ chosenTrackTokenAddress: undefined,
+ });
+ this.onCloseDialog();
+ return;
+ }
+ this.setState({
+ isAddingTokenToTracked: true,
+ });
+ const tokenAddress = this.state.chosenTrackTokenAddress;
+ const token = this.props.tokenByAddress[tokenAddress];
+ const newTokenEntry = _.assign({}, token);
+
+ newTokenEntry.isTracked = true;
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
+ this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
+
+ const [
+ balance,
+ allowance,
+ ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address);
+ this.props.dispatcher.updateTokenStateByAddress({
+ [token.address]: {
+ balance,
+ allowance,
+ },
+ });
+ this.setState({
+ isAddingTokenToTracked: false,
+ assetView: AssetViews.ASSET_PICKER,
+ chosenTrackTokenAddress: undefined,
+ });
+ this.props.onTokenChosen(tokenAddress);
+ }
+}
diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx
new file mode 100644
index 000000000..e9026d9bc
--- /dev/null
+++ b/packages/website/ts/components/generate_order/generate_order_form.tsx
@@ -0,0 +1,348 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {ZeroEx, Order} from '0x.js';
+import BigNumber from 'bignumber.js';
+import {Blockchain} from 'ts/blockchain';
+import Divider from 'material-ui/Divider';
+import Dialog from 'material-ui/Dialog';
+import {colors} from 'material-ui/styles';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {utils} from 'ts/utils/utils';
+import {SchemaValidator} from 'ts/schemas/validator';
+import {orderSchema} from 'ts/schemas/order_schema';
+import {Alert} from 'ts/components/ui/alert';
+import {OrderJSON} from 'ts/components/order_json';
+import {IdenticonAddressInput} from 'ts/components/inputs/identicon_address_input';
+import {TokenInput} from 'ts/components/inputs/token_input';
+import {TokenAmountInput} from 'ts/components/inputs/token_amount_input';
+import {HashInput} from 'ts/components/inputs/hash_input';
+import {ExpirationInput} from 'ts/components/inputs/expiration_input';
+import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {HelpTooltip} from 'ts/components/ui/help_tooltip';
+import {SwapIcon} from 'ts/components/ui/swap_icon';
+import {
+ Side,
+ SideToAssetToken,
+ SignatureData,
+ HashData,
+ TokenByAddress,
+ TokenStateByAddress,
+ BlockchainErrs,
+ Token,
+ AlertTypes,
+} from 'ts/types';
+
+enum SigningState {
+ UNSIGNED,
+ SIGNING,
+ SIGNED,
+}
+
+interface GenerateOrderFormProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ hashData: HashData;
+ orderExpiryTimestamp: BigNumber;
+ networkId: number;
+ userAddress: string;
+ orderSignatureData: SignatureData;
+ orderTakerAddress: string;
+ orderSalt: BigNumber;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+}
+
+interface GenerateOrderFormState {
+ globalErrMsg: string;
+ shouldShowIncompleteErrs: boolean;
+ signingState: SigningState;
+}
+
+const style = {
+ paper: {
+ display: 'inline-block',
+ position: 'relative',
+ textAlign: 'center',
+ width: '100%',
+ },
+};
+
+export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, any> {
+ private validator: SchemaValidator;
+ constructor(props: GenerateOrderFormProps) {
+ super(props);
+ this.state = {
+ globalErrMsg: '',
+ shouldShowIncompleteErrs: false,
+ signingState: SigningState.UNSIGNED,
+ };
+ this.validator = new SchemaValidator();
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ const dispatcher = this.props.dispatcher;
+ const depositTokenAddress = this.props.sideToAssetToken[Side.deposit].address;
+ const depositToken = this.props.tokenByAddress[depositTokenAddress];
+ const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress];
+ const receiveTokenAddress = this.props.sideToAssetToken[Side.receive].address;
+ const receiveToken = this.props.tokenByAddress[receiveTokenAddress];
+ const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress];
+ const takerExplanation = 'If a taker is specified, only they are<br> \
+ allowed to fill this order. If no taker is<br> \
+ specified, anyone is able to fill it.';
+ const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists();
+ return (
+ <div className="clearfix mb2 lg-px4 md-px4 sm-px2">
+ <h3>Generate an order</h3>
+ <Divider />
+ <div className="mx-auto" style={{maxWidth: 580}}>
+ <div className="pt3">
+ <div className="mx-auto clearfix">
+ <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2">
+ <TokenInput
+ userAddress={this.props.userAddress}
+ blockchain={this.props.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ label="Selling"
+ side={Side.deposit}
+ networkId={this.props.networkId}
+ assetToken={this.props.sideToAssetToken[Side.deposit]}
+ updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ <TokenAmountInput
+ label="Sell amount"
+ token={depositToken}
+ tokenState={depositTokenState}
+ amount={this.props.sideToAssetToken[Side.deposit].amount}
+ onChange={this.onTokenAmountChange.bind(this, depositToken, Side.deposit)}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={true}
+ />
+ </div>
+ <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide">
+ <div className="p1">
+ <SwapIcon
+ swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)}
+ />
+ </div>
+ </div>
+ <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2">
+ <TokenInput
+ userAddress={this.props.userAddress}
+ blockchain={this.props.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ label="Buying"
+ side={Side.receive}
+ networkId={this.props.networkId}
+ assetToken={this.props.sideToAssetToken[Side.receive]}
+ updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ <TokenAmountInput
+ label="Receive amount"
+ token={receiveToken}
+ tokenState={receiveTokenState}
+ amount={this.props.sideToAssetToken[Side.receive].amount}
+ onChange={this.onTokenAmountChange.bind(this, receiveToken, Side.receive)}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={false}
+ shouldCheckAllowance={false}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="pt1 sm-pb2 lg-px4 md-px4">
+ <div className="lg-px3 md-px3">
+ <div style={{fontSize: 12, color: colors.grey500}}>Expiration</div>
+ <ExpirationInput
+ orderExpiryTimestamp={this.props.orderExpiryTimestamp}
+ updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)}
+ />
+ </div>
+ </div>
+ <div className="pt1 flex mx-auto">
+ <IdenticonAddressInput
+ label="Taker"
+ initialAddress={this.props.orderTakerAddress}
+ updateOrderAddress={this.updateOrderAddress.bind(this)}
+ />
+ <div className="pt3">
+ <div className="pl1">
+ <HelpTooltip
+ explanation={takerExplanation}
+ />
+ </div>
+ </div>
+ </div>
+ <div>
+ <HashInput
+ blockchain={this.props.blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ hashData={this.props.hashData}
+ label="Order Hash"
+ />
+ </div>
+ <div className="pt2">
+ <div className="center">
+ <LifeCycleRaisedButton
+ labelReady="Sign hash"
+ labelLoading="Signing..."
+ labelComplete="Hash signed!"
+ onClickAsyncFn={this.onSignClickedAsync.bind(this)}
+ />
+ </div>
+ {this.state.globalErrMsg !== '' &&
+ <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
+ }
+ </div>
+ </div>
+ <Dialog
+ title="Order JSON"
+ titleStyle={{fontWeight: 100}}
+ modal={false}
+ open={this.state.signingState === SigningState.SIGNED}
+ onRequestClose={this.onCloseOrderJSONDialog.bind(this)}
+ >
+ <OrderJSON
+ exchangeContractIfExists={exchangeContractIfExists}
+ orderExpiryTimestamp={this.props.orderExpiryTimestamp}
+ orderSignatureData={this.props.orderSignatureData}
+ orderTakerAddress={this.props.orderTakerAddress}
+ orderMakerAddress={this.props.userAddress}
+ orderSalt={this.props.orderSalt}
+ orderMakerFee={this.props.hashData.makerFee}
+ orderTakerFee={this.props.hashData.takerFee}
+ orderFeeRecipient={this.props.hashData.feeRecipientAddress}
+ networkId={this.props.networkId}
+ sideToAssetToken={this.props.sideToAssetToken}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ </Dialog>
+ </div>
+ );
+ }
+ private onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) {
+ this.props.dispatcher.updateChosenAssetToken(side, {address: token.address, amount});
+ }
+ private onCloseOrderJSONDialog() {
+ // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store
+ // with a new value so that if a user signs the identical order again, the newly signed
+ // orderHash will not collide with the previously generated orderHash.
+ this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt());
+ this.setState({
+ signingState: SigningState.UNSIGNED,
+ });
+ }
+ private async onSignClickedAsync(): Promise<boolean> {
+ if (this.props.blockchainErr !== '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+
+ // Check if all required inputs were supplied
+ const debitToken = this.props.sideToAssetToken[Side.deposit];
+ const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance;
+ const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance;
+ const receiveAmount = this.props.sideToAssetToken[Side.receive].amount;
+ if (!_.isUndefined(debitToken.amount) && !_.isUndefined(receiveAmount) &&
+ debitToken.amount.gt(0) && receiveAmount.gt(0) &&
+ this.props.userAddress !== '' &&
+ debitBalance.gte(debitToken.amount) && debitAllowance.gte(debitToken.amount)) {
+ const didSignSuccessfully = await this.signTransactionAsync();
+ if (didSignSuccessfully) {
+ this.setState({
+ globalErrMsg: '',
+ shouldShowIncompleteErrs: false,
+ });
+ }
+ return didSignSuccessfully;
+ } else {
+ let globalErrMsg = 'You must fix the above errors in order to generate a valid order';
+ if (this.props.userAddress === '') {
+ globalErrMsg = 'You must enable wallet communication';
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ }
+ this.setState({
+ globalErrMsg,
+ shouldShowIncompleteErrs: true,
+ });
+ return false;
+ }
+ }
+ private async signTransactionAsync(): Promise<boolean> {
+ this.setState({
+ signingState: SigningState.SIGNING,
+ });
+ const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
+ if (_.isUndefined(exchangeContractAddr)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ isSigning: false,
+ });
+ return false;
+ }
+ const hashData = this.props.hashData;
+
+ const zeroExOrder: Order = {
+ exchangeContractAddress: exchangeContractAddr,
+ expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
+ feeRecipient: hashData.feeRecipientAddress,
+ maker: hashData.orderMakerAddress,
+ makerFee: hashData.makerFee,
+ makerTokenAddress: hashData.depositTokenContractAddr,
+ makerTokenAmount: hashData.depositAmount,
+ salt: hashData.orderSalt,
+ taker: hashData.orderTakerAddress,
+ takerFee: hashData.takerFee,
+ takerTokenAddress: hashData.receiveTokenContractAddr,
+ takerTokenAmount: hashData.receiveAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
+
+ let globalErrMsg = '';
+ try {
+ const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash);
+ const order = utils.generateOrder(this.props.networkId, exchangeContractAddr, this.props.sideToAssetToken,
+ hashData.orderExpiryTimestamp, this.props.orderTakerAddress,
+ this.props.userAddress, hashData.makerFee, hashData.takerFee,
+ hashData.feeRecipientAddress, signatureData, this.props.tokenByAddress,
+ hashData.orderSalt);
+ const validationResult = this.validator.validate(order, orderSchema);
+ if (validationResult.errors.length > 0) {
+ globalErrMsg = 'Order signing failed. Please refresh and try again';
+ utils.consoleLog(`Unexpected error occured: Order validation failed:
+ ${validationResult.errors}`);
+ }
+ } catch (err) {
+ const errMsg = '' + err;
+ if (utils.didUserDenyWeb3Request(errMsg)) {
+ globalErrMsg = 'User denied sign request';
+ } else {
+ globalErrMsg = 'An unexpected error occured. Please try refreshing the page';
+ utils.consoleLog(`Unexpected error occured: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ }
+ }
+ this.setState({
+ signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED,
+ globalErrMsg,
+ });
+ return globalErrMsg === '';
+ }
+ private updateOrderAddress(address?: string): void {
+ if (!_.isUndefined(address)) {
+ this.props.dispatcher.updateOrderTakerAddress(address);
+ }
+ }
+}
diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx
new file mode 100644
index 000000000..95c05f5bb
--- /dev/null
+++ b/packages/website/ts/components/generate_order/new_token_form.tsx
@@ -0,0 +1,237 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import TextField from 'material-ui/TextField';
+import {constants} from 'ts/utils/constants';
+import {Blockchain} from 'ts/blockchain';
+import {Token, TokenState, TokenByAddress, AlertTypes} from 'ts/types';
+import {AddressInput} from 'ts/components/inputs/address_input';
+import {Alert} from 'ts/components/ui/alert';
+import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button';
+import {RequiredLabel} from 'ts/components/ui/required_label';
+import BigNumber from 'bignumber.js';
+
+interface NewTokenFormProps {
+ blockchain: Blockchain;
+ tokenByAddress: TokenByAddress;
+ onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void;
+}
+
+interface NewTokenFormState {
+ globalErrMsg: string;
+ name: string;
+ nameErrText: string;
+ symbol: string;
+ symbolErrText: string;
+ address: string;
+ shouldShowAddressIncompleteErr: boolean;
+ decimals: string;
+ decimalsErrText: string;
+}
+
+export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFormState> {
+ constructor(props: NewTokenFormProps) {
+ super(props);
+ this.state = {
+ address: '',
+ globalErrMsg: '',
+ name: '',
+ nameErrText: '',
+ shouldShowAddressIncompleteErr: false,
+ symbol: '',
+ symbolErrText: '',
+ decimals: '18',
+ decimalsErrText: '',
+ };
+ }
+ public render() {
+ return (
+ <div className="mx-auto pb2" style={{width: 256}}>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500}}
+ floatingLabelText={<RequiredLabel label="Name" />}
+ value={this.state.name}
+ errorText={this.state.nameErrText}
+ onChange={this.onTokenNameChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500}}
+ floatingLabelText={<RequiredLabel label="Symbol" />}
+ value={this.state.symbol}
+ errorText={this.state.symbolErrText}
+ onChange={this.onTokenSymbolChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <AddressInput
+ isRequired={true}
+ label="Contract address"
+ initialAddress=""
+ shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr}
+ updateAddress={this.onTokenAddressChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500}}
+ floatingLabelText={<RequiredLabel label="Decimals" />}
+ value={this.state.decimals}
+ errorText={this.state.decimalsErrText}
+ onChange={this.onTokenDecimalsChanged.bind(this)}
+ />
+ </div>
+ <div className="pt2 mx-auto" style={{width: 120}}>
+ <LifeCycleRaisedButton
+ labelReady="Add"
+ labelLoading="Adding..."
+ labelComplete="Added!"
+ onClickAsyncFn={this.onAddNewTokenClickAsync.bind(this)}
+ />
+ </div>
+ {this.state.globalErrMsg !== '' &&
+ <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
+ }
+ </div>
+ );
+ }
+ private async onAddNewTokenClickAsync() {
+ // Trigger validation of name and symbol
+ this.onTokenNameChanged(undefined, this.state.name);
+ this.onTokenSymbolChanged(undefined, this.state.symbol);
+ this.onTokenDecimalsChanged(undefined, this.state.decimals);
+
+ const isAddressIncomplete = this.state.address === '';
+ let doesContractExist = false;
+ if (!isAddressIncomplete) {
+ doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address);
+ }
+
+ let hasBalanceAllowanceErr = false;
+ let balance = new BigNumber(0);
+ let allowance = new BigNumber(0);
+ if (doesContractExist) {
+ try {
+ [
+ balance,
+ allowance,
+ ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address);
+ } catch (err) {
+ hasBalanceAllowanceErr = true;
+ }
+ }
+
+ let globalErrMsg = '';
+ if (this.state.nameErrText !== '' || this.state.symbolErrText !== '' ||
+ this.state.decimalsErrText !== '' || isAddressIncomplete) {
+ globalErrMsg = 'Please fix the above issues';
+ } else if (!doesContractExist) {
+ globalErrMsg = 'No contract found at supplied address';
+ } else if (hasBalanceAllowanceErr) {
+ globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address';
+ } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) {
+ globalErrMsg = 'A token already exists with this address';
+ }
+
+ if (globalErrMsg !== '') {
+ this.setState({
+ globalErrMsg,
+ shouldShowAddressIncompleteErr: isAddressIncomplete,
+ });
+ return;
+ }
+
+ const newToken: Token = {
+ address: this.state.address,
+ decimals: _.parseInt(this.state.decimals),
+ iconUrl: undefined,
+ name: this.state.name,
+ symbol: this.state.symbol.toUpperCase(),
+ isTracked: true,
+ isRegistered: false,
+ };
+ const newTokenState: TokenState = {
+ balance,
+ allowance,
+ };
+ this.props.onNewTokenSubmitted(newToken, newTokenState);
+ }
+ private onTokenNameChanged(e: any, name: string) {
+ let nameErrText = '';
+ const maxLength = 30;
+ const tokens = _.values(this.props.tokenByAddress);
+ const tokenWithNameIfExists = _.find(tokens, {name});
+ const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists);
+ if (name === '') {
+ nameErrText = 'Name is required';
+ } else if (!this.isValidName(name)) {
+ nameErrText = 'Name should only contain letters, digits and spaces';
+ } else if (name.length > maxLength) {
+ nameErrText = `Max length is ${maxLength}`;
+ } else if (tokenWithNameExists) {
+ nameErrText = 'Token with this name already exists';
+ }
+
+ this.setState({
+ name,
+ nameErrText,
+ });
+ }
+ private onTokenSymbolChanged(e: any, symbol: string) {
+ let symbolErrText = '';
+ const maxLength = 5;
+ const tokens = _.values(this.props.tokenByAddress);
+ const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, {symbol}));
+ if (symbol === '') {
+ symbolErrText = 'Symbol is required';
+ } else if (!this.isLetters(symbol)) {
+ symbolErrText = 'Can only include letters';
+ } else if (symbol.length > maxLength) {
+ symbolErrText = `Max length is ${maxLength}`;
+ } else if (tokenWithSymbolExists) {
+ symbolErrText = 'Token with symbol already exists';
+ }
+
+ this.setState({
+ symbol,
+ symbolErrText,
+ });
+ }
+ private onTokenDecimalsChanged(e: any, decimals: string) {
+ let decimalsErrText = '';
+ const maxLength = 2;
+ if (decimals === '') {
+ decimalsErrText = 'Decimals is required';
+ } else if (!this.isInteger(decimals)) {
+ decimalsErrText = 'Must be an integer';
+ } else if (decimals.length > maxLength) {
+ decimalsErrText = `Max length is ${maxLength}`;
+ }
+
+ this.setState({
+ decimals,
+ decimalsErrText,
+ });
+ }
+ private onTokenAddressChanged(address?: string) {
+ if (!_.isUndefined(address)) {
+ this.setState({
+ address,
+ });
+ }
+ }
+ private isValidName(input: string) {
+ return /^[a-z0-9 ]+$/i.test(input);
+ }
+ private isInteger(input: string) {
+ return /^[0-9]+$/i.test(input);
+ }
+ private isLetters(input: string) {
+ return /^[a-zA-Z]+$/i.test(input);
+ }
+}
diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx
new file mode 100644
index 000000000..57ad7a5e2
--- /dev/null
+++ b/packages/website/ts/components/inputs/address_input.tsx
@@ -0,0 +1,74 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {isAddress} from 'ethereum-address';
+import TextField from 'material-ui/TextField';
+import {colors} from 'material-ui/styles';
+import {Blockchain} from 'ts/blockchain';
+import {RequiredLabel} from 'ts/components/ui/required_label';
+
+interface AddressInputProps {
+ disabled?: boolean;
+ initialAddress: string;
+ isRequired?: boolean;
+ hintText?: string;
+ shouldHideLabel?: boolean;
+ label?: string;
+ shouldShowIncompleteErrs?: boolean;
+ updateAddress: (address?: string) => void;
+}
+
+interface AddressInputState {
+ address: string;
+ errMsg: string;
+}
+
+export class AddressInput extends React.Component<AddressInputProps, AddressInputState> {
+ constructor(props: AddressInputProps) {
+ super(props);
+ this.state = {
+ address: this.props.initialAddress,
+ errMsg: '',
+ };
+ }
+ public componentWillReceiveProps(nextProps: AddressInputProps) {
+ if (nextProps.shouldShowIncompleteErrs && this.props.isRequired &&
+ this.state.address === '') {
+ this.setState({
+ errMsg: 'Address is required',
+ });
+ }
+ }
+ public render() {
+ const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> :
+ this.props.label;
+ const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block';
+ const hintText = this.props.hintText ? this.props.hintText : '';
+ return (
+ <div className="overflow-hidden">
+ <TextField
+ id={`address-field-${this.props.label}`}
+ disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled}
+ fullWidth={true}
+ hintText={hintText}
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500, display: labelDisplay}}
+ floatingLabelText={label}
+ errorText={this.state.errMsg}
+ value={this.state.address}
+ onChange={this.onOrderTakerAddressUpdated.bind(this)}
+ />
+ </div>
+ );
+ }
+ private onOrderTakerAddressUpdated(e: any) {
+ const address = e.target.value.toLowerCase();
+ const isValidAddress = isAddress(address) || address === '';
+ const errMsg = isValidAddress ? '' : 'Invalid ethereum address';
+ this.setState({
+ address,
+ errMsg,
+ });
+ const addressIfValid = isValidAddress ? address : undefined;
+ this.props.updateAddress(addressIfValid);
+ }
+}
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
new file mode 100644
index 000000000..f02112253
--- /dev/null
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -0,0 +1,94 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import Toggle from 'material-ui/Toggle';
+import {Blockchain} from 'ts/blockchain';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {Token, TokenState, BalanceErrs} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {errorReporter} from 'ts/utils/error_reporter';
+
+const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1);
+
+interface AllowanceToggleProps {
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ onErrorOccurred: (errType: BalanceErrs) => void;
+ token: Token;
+ tokenState: TokenState;
+ userAddress: string;
+}
+
+interface AllowanceToggleState {
+ isSpinnerVisible: boolean;
+ prevAllowance: BigNumber;
+}
+
+export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> {
+ constructor(props: AllowanceToggleProps) {
+ super(props);
+ this.state = {
+ isSpinnerVisible: false,
+ prevAllowance: props.tokenState.allowance,
+ };
+ }
+ public componentWillReceiveProps(nextProps: AllowanceToggleProps) {
+ if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) {
+ this.setState({
+ isSpinnerVisible: false,
+ prevAllowance: nextProps.tokenState.allowance,
+ });
+ }
+ }
+ public render() {
+ return (
+ <div className="flex">
+ <div>
+ <Toggle
+ disabled={this.state.isSpinnerVisible}
+ toggled={this.isAllowanceSet()}
+ onToggle={this.onToggleAllowanceAsync.bind(this, this.props.token)}
+ />
+ </div>
+ {this.state.isSpinnerVisible &&
+ <div className="pl1" style={{paddingTop: 3}}>
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </div>
+ }
+ </div>
+ );
+ }
+ private async onToggleAllowanceAsync() {
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+
+ this.setState({
+ isSpinnerVisible: true,
+ });
+
+ let newAllowanceAmountInBaseUnits = new BigNumber(0);
+ if (!this.isAllowanceSet()) {
+ newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
+ }
+ try {
+ await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
+ } catch (err) {
+ this.setState({
+ isSpinnerVisible: false,
+ });
+ const errMsg = '' + err;
+ if (_.includes(errMsg, 'User denied transaction')) {
+ return false;
+ }
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
+ }
+ }
+ private isAllowanceSet() {
+ return !this.props.tokenState.allowance.eq(0);
+ }
+}
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
new file mode 100644
index 000000000..1c8b410a4
--- /dev/null
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -0,0 +1,160 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import {ValidatedBigNumberCallback, InputErrMsg, WebsitePaths} from 'ts/types';
+import TextField from 'material-ui/TextField';
+import {RequiredLabel} from 'ts/components/ui/required_label';
+import {colors} from 'material-ui/styles';
+import {utils} from 'ts/utils/utils';
+import {Link} from 'react-router-dom';
+
+interface BalanceBoundedInputProps {
+ label?: string;
+ balance: BigNumber;
+ amount?: BigNumber;
+ onChange: ValidatedBigNumberCallback;
+ shouldShowIncompleteErrs?: boolean;
+ shouldCheckBalance: boolean;
+ validate?: (amount: BigNumber) => InputErrMsg;
+ onVisitBalancesPageClick?: () => void;
+ shouldHideVisitBalancesLink?: boolean;
+}
+
+interface BalanceBoundedInputState {
+ errMsg: InputErrMsg;
+ amountString: string;
+}
+
+export class BalanceBoundedInput extends
+ React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> {
+ public static defaultProps: Partial<BalanceBoundedInputProps> = {
+ shouldShowIncompleteErrs: false,
+ shouldHideVisitBalancesLink: false,
+ };
+ constructor(props: BalanceBoundedInputProps) {
+ super(props);
+ const amountString = this.props.amount ? this.props.amount.toString() : '';
+ this.state = {
+ errMsg: this.validate(amountString, props.balance),
+ amountString,
+ };
+ }
+ public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) {
+ if (nextProps === this.props) {
+ return;
+ }
+ const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString);
+ if (!_.isUndefined(nextProps.amount)) {
+ let shouldResetState = false;
+ if (!isCurrentAmountNumeric) {
+ shouldResetState = true;
+ } else {
+ const currentAmount = new BigNumber(this.state.amountString);
+ if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) {
+ shouldResetState = true;
+ }
+ }
+ if (shouldResetState) {
+ const amountString = nextProps.amount.toString();
+ this.setState({
+ errMsg: this.validate(amountString, nextProps.balance),
+ amountString,
+ });
+ }
+ } else if (isCurrentAmountNumeric) {
+ const amountString = '';
+ this.setState({
+ errMsg: this.validate(amountString, nextProps.balance),
+ amountString,
+ });
+ }
+ }
+ public render() {
+ let errorText = this.state.errMsg;
+ if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') {
+ errorText = 'This field is required';
+ }
+ let label: React.ReactNode|string = '';
+ if (!_.isUndefined(this.props.label)) {
+ label = <RequiredLabel label={this.props.label}/>;
+ }
+ return (
+ <TextField
+ fullWidth={true}
+ floatingLabelText={label}
+ floatingLabelFixed={true}
+ floatingLabelStyle={{color: colors.grey500, width: 206}}
+ errorText={errorText}
+ value={this.state.amountString}
+ hintText={<span style={{textTransform: 'capitalize'}}>amount</span>}
+ onChange={this.onValueChange.bind(this)}
+ underlineStyle={{width: 'calc(100% + 50px)'}}
+ />
+ );
+ }
+ private onValueChange(e: any, amountString: string) {
+ const errMsg = this.validate(amountString, this.props.balance);
+ this.setState({
+ amountString,
+ errMsg,
+ }, () => {
+ const isValid = _.isUndefined(errMsg);
+ if (utils.isNumeric(amountString)) {
+ this.props.onChange(isValid, new BigNumber(amountString));
+ } else {
+ this.props.onChange(isValid);
+ }
+ });
+ }
+ private validate(amountString: string, balance: BigNumber): InputErrMsg {
+ if (!utils.isNumeric(amountString)) {
+ return amountString !== '' ? 'Must be a number' : '';
+ }
+ const amount = new BigNumber(amountString);
+ if (amount.eq(0)) {
+ return 'Cannot be zero';
+ }
+ if (this.props.shouldCheckBalance && amount.gt(balance)) {
+ return (
+ <span>
+ Insufficient balance.{' '}
+ {this.renderIncreaseBalanceLink()}
+ </span>
+ );
+ }
+ const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount);
+ return errMsg;
+ }
+ private renderIncreaseBalanceLink() {
+ if (this.props.shouldHideVisitBalancesLink) {
+ return null;
+ }
+
+ const increaseBalanceText = 'Increase balance';
+ const linkStyle = {
+ cursor: 'pointer',
+ color: colors.grey900,
+ textDecoration: 'underline',
+ display: 'inline',
+ };
+ if (_.isUndefined(this.props.onVisitBalancesPageClick)) {
+ return (
+ <Link
+ to={`${WebsitePaths.Portal}/balances`}
+ style={linkStyle}
+ >
+ {increaseBalanceText}
+ </Link>
+ );
+ } else {
+ return (
+ <div
+ onClick={this.props.onVisitBalancesPageClick}
+ style={linkStyle}
+ >
+ {increaseBalanceText}
+ </div>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx
new file mode 100644
index 000000000..ad551e125
--- /dev/null
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -0,0 +1,51 @@
+import BigNumber from 'bignumber.js';
+import * as _ from 'lodash';
+import * as React from 'react';
+import {ZeroEx} from '0x.js';
+import {ValidatedBigNumberCallback} from 'ts/types';
+import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input';
+import {constants} from 'ts/utils/constants';
+
+interface EthAmountInputProps {
+ label?: string;
+ balance: BigNumber;
+ amount?: BigNumber;
+ onChange: ValidatedBigNumberCallback;
+ shouldShowIncompleteErrs: boolean;
+ onVisitBalancesPageClick?: () => void;
+ shouldCheckBalance: boolean;
+ shouldHideVisitBalancesLink?: boolean;
+}
+
+interface EthAmountInputState {}
+
+export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> {
+ public render() {
+ const amount = this.props.amount ?
+ ZeroEx.toUnitAmount(this.props.amount, constants.ETH_DECIMAL_PLACES) :
+ undefined;
+ return (
+ <div className="flex overflow-hidden" style={{height: 63}}>
+ <BalanceBoundedInput
+ label={this.props.label}
+ balance={this.props.balance}
+ amount={amount}
+ onChange={this.onChange.bind(this)}
+ shouldCheckBalance={this.props.shouldCheckBalance}
+ shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
+ onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
+ shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink}
+ />
+ <div style={{paddingTop: _.isUndefined(this.props.label) ? 15 : 40}}>
+ ETH
+ </div>
+ </div>
+ );
+ }
+ private onChange(isValid: boolean, amount?: BigNumber) {
+ const baseUnitAmountIfExists = _.isUndefined(amount) ?
+ undefined :
+ ZeroEx.toBaseUnitAmount(amount, constants.ETH_DECIMAL_PLACES);
+ this.props.onChange(isValid, baseUnitAmountIfExists);
+ }
+}
diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx
new file mode 100644
index 000000000..32dcad189
--- /dev/null
+++ b/packages/website/ts/components/inputs/expiration_input.tsx
@@ -0,0 +1,108 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+import DatePicker from 'material-ui/DatePicker';
+import TimePicker from 'material-ui/TimePicker';
+import {utils} from 'ts/utils/utils';
+import BigNumber from 'bignumber.js';
+import * as moment from 'moment';
+
+interface ExpirationInputProps {
+ orderExpiryTimestamp: BigNumber;
+ updateOrderExpiry: (unixTimestampSec: BigNumber) => void;
+}
+
+interface ExpirationInputState {
+ dateMoment: moment.Moment;
+ timeMoment: moment.Moment;
+}
+
+export class ExpirationInput extends React.Component<ExpirationInputProps, ExpirationInputState> {
+ private earliestPickableMoment: moment.Moment;
+ constructor(props: ExpirationInputProps) {
+ super(props);
+ // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates
+ this.earliestPickableMoment = moment().startOf('day');
+ const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp);
+ const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
+ const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp);
+ this.state = {
+ dateMoment: didUserSetExpiry ? expirationMoment : undefined,
+ timeMoment: didUserSetExpiry ? expirationMoment : undefined,
+ };
+ }
+ public render() {
+ const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined;
+ const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined;
+ return (
+ <div className="clearfix">
+ <div className="col col-6 overflow-hidden pr3 flex relative">
+ <DatePicker
+ className="overflow-hidden"
+ hintText="Date"
+ mode="landscape"
+ autoOk={true}
+ value={date}
+ onChange={this.onDateChanged.bind(this)}
+ shouldDisableDate={this.shouldDisableDate.bind(this)}
+ />
+ <div
+ className="absolute"
+ style={{fontSize: 20, right: 40, top: 13, pointerEvents: 'none'}}
+ >
+ <i className="zmdi zmdi-calendar" />
+ </div>
+ </div>
+ <div className="col col-5 overflow-hidden flex relative">
+ <TimePicker
+ className="overflow-hidden"
+ hintText="Time"
+ autoOk={true}
+ value={time}
+ onChange={this.onTimeChanged.bind(this)}
+ />
+ <div
+ className="absolute"
+ style={{fontSize: 20, right: 9, top: 13, pointerEvents: 'none'}}
+ >
+ <i className="zmdi zmdi-time" />
+ </div>
+ </div>
+ <div
+ onClick={this.clearDates.bind(this)}
+ className="col col-1 pt2"
+ style={{textAlign: 'right'}}
+ >
+ <i style={{fontSize: 16, cursor: 'pointer'}} className="zmdi zmdi-close" />
+ </div>
+ </div>
+ );
+ }
+ private shouldDisableDate(date: Date): boolean {
+ return moment(date).startOf('day').isBefore(this.earliestPickableMoment);
+ }
+ private clearDates() {
+ this.setState({
+ dateMoment: undefined,
+ timeMoment: undefined,
+ });
+ const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec();
+ this.props.updateOrderExpiry(defaultDateTime);
+ }
+ private onDateChanged(e: any, date: Date) {
+ const dateMoment = moment(date);
+ this.setState({
+ dateMoment,
+ });
+ const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment);
+ this.props.updateOrderExpiry(timestamp);
+ }
+ private onTimeChanged(e: any, time: Date) {
+ const timeMoment = moment(time);
+ this.setState({
+ timeMoment,
+ });
+ const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment;
+ const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment);
+ this.props.updateOrderExpiry(timestamp);
+ }
+}
diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx
new file mode 100644
index 000000000..3e42f1d5f
--- /dev/null
+++ b/packages/website/ts/components/inputs/hash_input.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import {Blockchain} from 'ts/blockchain';
+import {ZeroEx, Order} from '0x.js';
+import {FakeTextField} from 'ts/components/ui/fake_text_field';
+import ReactTooltip = require('react-tooltip');
+import {HashData, Styles} from 'ts/types';
+import {constants} from 'ts/utils/constants';
+
+const styles: Styles = {
+ textField: {
+ overflow: 'hidden',
+ paddingTop: 8,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ },
+};
+
+interface HashInputProps {
+ blockchain: Blockchain;
+ blockchainIsLoaded: boolean;
+ hashData: HashData;
+ label: string;
+}
+
+interface HashInputState {}
+
+export class HashInput extends React.Component<HashInputProps, HashInputState> {
+ public render() {
+ const msgHashHex = this.props.blockchainIsLoaded ? this.generateMessageHashHex() : '';
+ return (
+ <div>
+ <FakeTextField label={this.props.label}>
+ <div
+ style={styles.textField}
+ data-tip={true}
+ data-for="hashTooltip"
+ >
+ {msgHashHex}
+ </div>
+ </FakeTextField>
+ <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip>
+ </div>
+ );
+ }
+ private generateMessageHashHex() {
+ const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists();
+ const hashData = this.props.hashData;
+ const order: Order = {
+ exchangeContractAddress,
+ expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
+ feeRecipient: hashData.feeRecipientAddress,
+ maker: hashData.orderMakerAddress,
+ makerFee: hashData.makerFee,
+ makerTokenAddress: hashData.depositTokenContractAddr,
+ makerTokenAmount: hashData.depositAmount,
+ salt: hashData.orderSalt,
+ taker: hashData.orderTakerAddress,
+ takerFee: hashData.takerFee,
+ takerTokenAddress: hashData.receiveTokenContractAddr,
+ takerTokenAmount: hashData.receiveAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(order);
+ return orderHash;
+ }
+}
diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx
new file mode 100644
index 000000000..6452f5fe9
--- /dev/null
+++ b/packages/website/ts/components/inputs/identicon_address_input.tsx
@@ -0,0 +1,56 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {Blockchain} from 'ts/blockchain';
+import {Identicon} from 'ts/components/ui/identicon';
+import {RequiredLabel} from 'ts/components/ui/required_label';
+import {AddressInput} from 'ts/components/inputs/address_input';
+import {InputLabel} from 'ts/components/ui/input_label';
+
+interface IdenticonAddressInputProps {
+ initialAddress: string;
+ isRequired?: boolean;
+ label: string;
+ updateOrderAddress: (address?: string) => void;
+}
+
+interface IdenticonAddressInputState {
+ address: string;
+}
+
+export class IdenticonAddressInput extends React.Component<IdenticonAddressInputProps, IdenticonAddressInputState> {
+ constructor(props: IdenticonAddressInputProps) {
+ super(props);
+ this.state = {
+ address: props.initialAddress,
+ };
+ }
+ public render() {
+ const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> :
+ this.props.label;
+ return (
+ <div className="relative" style={{width: '100%'}}>
+ <InputLabel text={label} />
+ <div className="flex">
+ <div className="col col-1 pb1 pr1" style={{paddingTop: 13}}>
+ <Identicon address={this.state.address} diameter={26} />
+ </div>
+ <div className="col col-11 pb1 pl1" style={{height: 65}}>
+ <AddressInput
+ hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..."
+ shouldHideLabel={true}
+ initialAddress={this.props.initialAddress}
+ updateAddress={this.updateAddress.bind(this)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private updateAddress(address?: string): void {
+ this.setState({
+ address,
+ });
+ this.props.updateOrderAddress(address);
+ }
+}
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
new file mode 100644
index 000000000..e19af8984
--- /dev/null
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+import BigNumber from 'bignumber.js';
+import {ZeroEx} from '0x.js';
+import {Link} from 'react-router-dom';
+import {colors} from 'material-ui/styles';
+import {Token, TokenState, InputErrMsg, ValidatedBigNumberCallback, WebsitePaths} from 'ts/types';
+import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input';
+
+interface TokenAmountInputProps {
+ label: string;
+ token: Token;
+ tokenState: TokenState;
+ amount?: BigNumber;
+ shouldShowIncompleteErrs: boolean;
+ shouldCheckBalance: boolean;
+ shouldCheckAllowance: boolean;
+ onChange: ValidatedBigNumberCallback;
+ onVisitBalancesPageClick?: () => void;
+}
+
+interface TokenAmountInputState {}
+
+export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> {
+ public render() {
+ const amount = this.props.amount ?
+ ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) :
+ undefined;
+ return (
+ <div className="flex overflow-hidden" style={{height: 84}}>
+ <BalanceBoundedInput
+ label={this.props.label}
+ amount={amount}
+ balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)}
+ onChange={this.onChange.bind(this)}
+ validate={this.validate.bind(this)}
+ shouldCheckBalance={this.props.shouldCheckBalance}
+ shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
+ onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
+ />
+ <div style={{paddingTop: 39}}>
+ {this.props.token.symbol}
+ </div>
+ </div>
+ );
+ }
+ private onChange(isValid: boolean, amount?: BigNumber) {
+ let baseUnitAmount;
+ if (!_.isUndefined(amount)) {
+ baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals);
+ }
+ this.props.onChange(isValid, baseUnitAmount);
+ }
+ private validate(amount: BigNumber): InputErrMsg {
+ if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) {
+ return (
+ <span>
+ Insufficient allowance.{' '}
+ <Link
+ to={`${WebsitePaths.Portal}/balances`}
+ style={{cursor: 'pointer', color: colors.grey900}}
+ >
+ Set allowance
+ </Link>
+ </span>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx
new file mode 100644
index 000000000..2be74d4fd
--- /dev/null
+++ b/packages/website/ts/components/inputs/token_input.tsx
@@ -0,0 +1,107 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import Paper from 'material-ui/Paper';
+import {colors} from 'material-ui/styles';
+import {Blockchain} from 'ts/blockchain';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {AssetToken, Side, TokenByAddress, BlockchainErrs, Token, TokenState} from 'ts/types';
+import {AssetPicker} from 'ts/components/generate_order/asset_picker';
+import {InputLabel} from 'ts/components/ui/input_label';
+import {TokenIcon} from 'ts/components/ui/token_icon';
+
+const TOKEN_ICON_DIMENSION = 80;
+
+interface TokenInputProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ dispatcher: Dispatcher;
+ label: string;
+ side: Side;
+ networkId: number;
+ assetToken: AssetToken;
+ updateChosenAssetToken: (side: Side, token: AssetToken) => void;
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
+}
+
+interface TokenInputState {
+ isHoveringIcon: boolean;
+ isPickerOpen: boolean;
+ trackCandidateTokenIfExists?: Token;
+}
+
+export class TokenInput extends React.Component<TokenInputProps, TokenInputState> {
+ constructor(props: TokenInputProps) {
+ super(props);
+ this.state = {
+ isHoveringIcon: false,
+ isPickerOpen: false,
+ };
+ }
+ public render() {
+ const token = this.props.tokenByAddress[this.props.assetToken.address];
+ const iconStyles = {
+ cursor: 'pointer',
+ opacity: this.state.isHoveringIcon ? 0.5 : 1,
+ };
+ return (
+ <div className="relative">
+ <div className="pb1">
+ <InputLabel text={this.props.label} />
+ </div>
+ <Paper
+ zDepth={1}
+ style={{cursor: 'pointer'}}
+ onMouseEnter={this.onToggleHover.bind(this, true)}
+ onMouseLeave={this.onToggleHover.bind(this, false)}
+ onClick={this.onAssetClicked.bind(this)}
+ >
+ <div
+ className="mx-auto pt2"
+ style={{width: TOKEN_ICON_DIMENSION, ...iconStyles}}
+ >
+ <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
+ </div>
+ <div className="py1 center" style={{color: colors.grey500}}>
+ {token.name}
+ </div>
+ </Paper>
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isPickerOpen}
+ currentTokenAddress={this.props.assetToken.address}
+ onTokenChosen={this.onTokenChosen.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ </div>
+ );
+ }
+ private onTokenChosen(tokenAddress: string) {
+ const assetToken: AssetToken = {
+ address: tokenAddress,
+ amount: this.props.assetToken.amount,
+ };
+ this.props.updateChosenAssetToken(this.props.side, assetToken);
+ this.setState({
+ isPickerOpen: false,
+ });
+ }
+ private onToggleHover(isHoveringIcon: boolean) {
+ this.setState({
+ isHoveringIcon,
+ });
+ }
+ private onAssetClicked() {
+ if (this.props.blockchainErr !== '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
+
+ this.setState({
+ isPickerOpen: true,
+ });
+ }
+}
diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx
new file mode 100644
index 000000000..90e3543dd
--- /dev/null
+++ b/packages/website/ts/components/order_json.tsx
@@ -0,0 +1,164 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import {utils} from 'ts/utils/utils';
+import {colors} from 'material-ui/styles';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import TextField from 'material-ui/TextField';
+import Paper from 'material-ui/Paper';
+import {CopyIcon} from 'ts/components/ui/copy_icon';
+import {SideToAssetToken, SignatureData, Order, TokenByAddress, WebsitePaths} from 'ts/types';
+import {errorReporter} from 'ts/utils/error_reporter';
+
+interface OrderJSONProps {
+ exchangeContractIfExists: string;
+ orderExpiryTimestamp: BigNumber;
+ orderSignatureData: SignatureData;
+ orderTakerAddress: string;
+ orderMakerAddress: string;
+ orderSalt: BigNumber;
+ orderMakerFee: BigNumber;
+ orderTakerFee: BigNumber;
+ orderFeeRecipient: string;
+ networkId: number;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
+}
+
+interface OrderJSONState {
+ shareLink: string;
+}
+
+export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> {
+ constructor(props: OrderJSONProps) {
+ super(props);
+ this.state = {
+ shareLink: '',
+ };
+ this.setShareLinkAsync();
+ }
+ public render() {
+ const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists,
+ this.props.sideToAssetToken, this.props.orderExpiryTimestamp,
+ this.props.orderTakerAddress, this.props.orderMakerAddress,
+ this.props.orderMakerFee, this.props.orderTakerFee,
+ this.props.orderFeeRecipient, this.props.orderSignatureData,
+ this.props.tokenByAddress, this.props.orderSalt);
+ const orderJSON = JSON.stringify(order);
+ return (
+ <div>
+ <div className="pb2">
+ You have successfully generated and cryptographically signed an order! The{' '}
+ following JSON contains the order parameters and cryptographic signature that{' '}
+ your counterparty will need to execute a trade with you.
+ </div>
+ <div className="pb2 flex">
+ <div
+ className="inline-block pl1"
+ style={{top: 1}}
+ >
+ <CopyIcon data={orderJSON} callToAction="Copy" />
+ </div>
+ </div>
+ <Paper className="center overflow-hidden">
+ <TextField
+ id="orderJSON"
+ style={{width: 710}}
+ value={JSON.stringify(order, null, '\t')}
+ multiLine={true}
+ rows={2}
+ rowsMax={8}
+ underlineStyle={{display: 'none'}}
+ />
+ </Paper>
+ <div className="pt3 pb2 center">
+ <div>
+ Share your signed order!
+ </div>
+ <div>
+ <div className="mx-auto overflow-hidden" style={{width: 152}}>
+ <TextField
+ id={`${this.state.shareLink}-bitly`}
+ value={this.state.shareLink}
+ />
+ </div>
+ </div>
+ <div className="mx-auto pt1 flex" style={{width: 91}}>
+ <div>
+ <i
+ style={{cursor: 'pointer', fontSize: 29}}
+ onClick={this.shareViaFacebook.bind(this)}
+ className="zmdi zmdi-facebook-box"
+ />
+ </div>
+ <div className="pl1" style={{position: 'relative', width: 28}}>
+ <i
+ style={{cursor: 'pointer', fontSize: 32, position: 'absolute', top: -2, left: 8}}
+ onClick={this.shareViaEmailAsync.bind(this)}
+ className="zmdi zmdi-email"
+ />
+ </div>
+ <div className="pl1">
+ <i
+ style={{cursor: 'pointer', fontSize: 29}}
+ onClick={this.shareViaTwitterAsync.bind(this)}
+ className="zmdi zmdi-twitter-box"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private async shareViaTwitterAsync() {
+ const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`);
+ window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400');
+ }
+ private async shareViaFacebook() {
+ (window as any).FB.ui({
+ display: 'popup',
+ href: this.state.shareLink,
+ method: 'share',
+ }, _.noop);
+ }
+ private async shareViaEmailAsync() {
+ const encodedSubject = encodeURIComponent('Let\'s trade using the 0x protocol');
+ const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol.
+You can see and fill it here: ${this.state.shareLink}`);
+ const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`;
+ window.open(mailToLink, '_blank');
+ }
+ private async setShareLinkAsync() {
+ const shareLink = await this.generateShareLinkAsync();
+ this.setState({
+ shareLink,
+ });
+ }
+ private async generateShareLinkAsync(): Promise<string> {
+ const longUrl = encodeURIComponent(this.getOrderUrl());
+ const bitlyRequestUrl = constants.BITLY_ENDPOINT + '/v3/shorten?' +
+ 'access_token=' + constants.BITLY_ACCESS_TOKEN +
+ '&longUrl=' + longUrl;
+ const response = await fetch(bitlyRequestUrl);
+ const responseBody = await response.text();
+ const bodyObj = JSON.parse(responseBody);
+ if (response.status !== 200 || bodyObj.status_code !== 200) {
+ // TODO: Show error message in UI
+ utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`);
+ await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`));
+ return '';
+ }
+ return (bodyObj as any).data.url;
+ }
+ private getOrderUrl() {
+ const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists,
+ this.props.sideToAssetToken, this.props.orderExpiryTimestamp, this.props.orderTakerAddress,
+ this.props.orderMakerAddress, this.props.orderMakerFee, this.props.orderTakerFee,
+ this.props.orderFeeRecipient, this.props.orderSignatureData, this.props.tokenByAddress,
+ this.props.orderSalt);
+ const orderJSONString = JSON.stringify(order);
+ const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`;
+ return orderUrl;
+ }
+}
diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx
new file mode 100644
index 000000000..3591a347b
--- /dev/null
+++ b/packages/website/ts/components/portal.tsx
@@ -0,0 +1,344 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+import {Switch, Route} from 'react-router-dom';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {State} from 'ts/redux/reducer';
+import {utils} from 'ts/utils/utils';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import Paper from 'material-ui/Paper';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {GenerateOrderForm} from 'ts/containers/generate_order_form';
+import {TokenBalances} from 'ts/components/token_balances';
+import {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog';
+import {FillOrder} from 'ts/components/fill_order';
+import {Blockchain} from 'ts/blockchain';
+import {SchemaValidator} from 'ts/schemas/validator';
+import {orderSchema} from 'ts/schemas/order_schema';
+import {localStorage} from 'ts/local_storage/local_storage';
+import {TradeHistory} from 'ts/components/trade_history/trade_history';
+import {
+ HashData,
+ TokenByAddress,
+ BlockchainErrs,
+ Order,
+ Fill,
+ Side,
+ Styles,
+ ScreenWidths,
+ Token,
+ TokenStateByAddress,
+ WebsitePaths,
+} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {Footer} from 'ts/components/footer';
+import {Loading} from 'ts/components/ui/loading';
+import {PortalMenu} from 'ts/components/portal_menu';
+import {BlockchainErrDialog} from 'ts/components/dialogs/blockchain_err_dialog';
+import BigNumber from 'bignumber.js';
+import {FlashMessage} from 'ts/components/ui/flash_message';
+
+const THROTTLE_TIMEOUT = 100;
+
+export interface PortalPassedProps {}
+
+export interface PortalAllProps {
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ hashData: HashData;
+ networkId: number;
+ nodeVersion: string;
+ orderFillAmount: BigNumber;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userEtherBalance: BigNumber;
+ userAddress: string;
+ shouldBlockchainErrDialogBeOpen: boolean;
+ userSuppliedOrderCache: Order;
+ location: Location;
+ flashMessage?: string|React.ReactNode;
+}
+
+interface PortalAllState {
+ prevNetworkId: number;
+ prevNodeVersion: string;
+ prevUserAddress: string;
+ hasAcceptedDisclaimer: boolean;
+}
+
+const styles: Styles = {
+ button: {
+ color: 'white',
+ },
+ headline: {
+ fontSize: 20,
+ fontWeight: 400,
+ marginBottom: 12,
+ paddingTop: 16,
+ },
+ inkBar: {
+ background: colors.amber600,
+ },
+ menuItem: {
+ padding: '0px 16px 0px 48px',
+ },
+ tabItemContainer: {
+ background: colors.blueGrey500,
+ borderRadius: '4px 4px 0 0',
+ },
+};
+
+export class Portal extends React.Component<PortalAllProps, PortalAllState> {
+ private blockchain: Blockchain;
+ private sharedOrderIfExists: Order;
+ private throttledScreenWidthUpdate: () => void;
+ constructor(props: PortalAllProps) {
+ super(props);
+ this.sharedOrderIfExists = this.getSharedOrderIfExists();
+ this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
+ this.state = {
+ prevNetworkId: this.props.networkId,
+ prevNodeVersion: this.props.nodeVersion,
+ prevUserAddress: this.props.userAddress,
+ hasAcceptedDisclaimer: false,
+ };
+ }
+ public componentDidMount() {
+ window.addEventListener('resize', this.throttledScreenWidthUpdate);
+ window.scrollTo(0, 0);
+ }
+ public componentWillMount() {
+ this.blockchain = new Blockchain(this.props.dispatcher);
+ const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY);
+ const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) &&
+ !_.isEmpty(didAcceptPortalDisclaimer);
+ this.setState({
+ hasAcceptedDisclaimer,
+ });
+ }
+ public componentWillUnmount() {
+ this.blockchain.destroy();
+ window.removeEventListener('resize', this.throttledScreenWidthUpdate);
+ // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered
+ // the initialization process always occurs from the same base state. This helps avoid
+ // initialization inconsistencies (i.e While the portal was unrendered, the user might have
+ // become disconnected from their backing Ethereum node, changes user accounts, etc...)
+ this.props.dispatcher.resetState();
+ }
+ public componentWillReceiveProps(nextProps: PortalAllProps) {
+ if (nextProps.networkId !== this.state.prevNetworkId) {
+ this.blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId);
+ this.setState({
+ prevNetworkId: nextProps.networkId,
+ });
+ }
+ if (nextProps.userAddress !== this.state.prevUserAddress) {
+ this.blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress);
+ if (!_.isEmpty(nextProps.userAddress) &&
+ nextProps.blockchainIsLoaded) {
+ const tokens = _.values(nextProps.tokenByAddress);
+ this.updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
+ }
+ this.setState({
+ prevUserAddress: nextProps.userAddress,
+ });
+ }
+ if (nextProps.nodeVersion !== this.state.prevNodeVersion) {
+ this.blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion);
+ }
+ }
+ public render() {
+ const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher
+ .updateShouldBlockchainErrDialogBeOpen.bind(this.props.dispatcher);
+ const portalStyle: React.CSSProperties = {
+ minHeight: '100vh',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ };
+ return (
+ <div style={portalStyle}>
+ <DocumentTitle title="0x Portal DApp"/>
+ <TopBar
+ userAddress={this.props.userAddress}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ location={this.props.location}
+ />
+ <div id="portal" className="mx-auto max-width-4 pt4" style={{width: '100%'}}>
+ <Paper className="mb3 mt2">
+ {!configs.isMainnetEnabled && this.props.networkId === constants.MAINNET_NETWORK_ID ?
+ <div className="p3 center">
+ <div className="h2 py2">Mainnet unavailable</div>
+ <div className="mx-auto pb2 pt2">
+ <img
+ src="/images/zrx_token.png"
+ style={{width: 150}}
+ />
+ </div>
+ <div>
+ 0x portal is currently unavailable on the Ethereum mainnet.
+ <div>
+ To try it out, switch to the Kovan test network
+ (networkId: 42).
+ </div>
+ <div className="py2">
+ Check back soon!
+ </div>
+ </div>
+ </div> :
+ <div className="mx-auto flex">
+ <div
+ className="col col-2 pr2 pt1 sm-hide xs-hide"
+ style={{overflow: 'hidden', backgroundColor: 'rgb(39, 39, 39)', color: 'white'}}
+ >
+ <PortalMenu menuItemStyle={{color: 'white'}} />
+ </div>
+ <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12">
+ <div className="py2" style={{backgroundColor: colors.grey50}}>
+ {this.props.blockchainIsLoaded ?
+ <Switch>
+ <Route
+ path={`${WebsitePaths.Portal}/fill`}
+ render={this.renderFillOrder.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Portal}/balances`}
+ render={this.renderTokenBalances.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Portal}/trades`}
+ component={this.renderTradeHistory.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Home}`}
+ render={this.renderGenerateOrderForm.bind(this)}
+ />
+ </Switch> :
+ <Loading />
+ }
+ </div>
+ </div>
+ </div>
+ }
+ </Paper>
+ <BlockchainErrDialog
+ blockchain={this.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ isOpen={this.props.shouldBlockchainErrDialogBeOpen}
+ userAddress={this.props.userAddress}
+ toggleDialogFn={updateShouldBlockchainErrDialogBeOpen}
+ networkId={this.props.networkId}
+ />
+ <PortalDisclaimerDialog
+ isOpen={!this.state.hasAcceptedDisclaimer}
+ onToggleDialog={this.onPortalDisclaimerAccepted.bind(this)}
+ />
+ <FlashMessage
+ dispatcher={this.props.dispatcher}
+ flashMessage={this.props.flashMessage}
+ />
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderTradeHistory() {
+ return (
+ <TradeHistory
+ tokenByAddress={this.props.tokenByAddress}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ />
+ );
+ }
+ private renderTokenBalances() {
+ return (
+ <TokenBalances
+ blockchain={this.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ dispatcher={this.props.dispatcher}
+ screenWidth={this.props.screenWidth}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenStateByAddress={this.props.tokenStateByAddress}
+ userAddress={this.props.userAddress}
+ userEtherBalance={this.props.userEtherBalance}
+ networkId={this.props.networkId}
+ />
+ );
+ }
+ private renderFillOrder(match: any, location: Location, history: History) {
+ const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ?
+ this.props.userSuppliedOrderCache :
+ this.sharedOrderIfExists;
+ return (
+ <FillOrder
+ blockchain={this.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ initialOrder={initialFillOrder}
+ isOrderInUrl={!_.isUndefined(this.sharedOrderIfExists)}
+ orderFillAmount={this.props.orderFillAmount}
+ networkId={this.props.networkId}
+ userAddress={this.props.userAddress}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenStateByAddress={this.props.tokenStateByAddress}
+ dispatcher={this.props.dispatcher}
+ />
+ );
+ }
+ private renderGenerateOrderForm(match: any, location: Location, history: History) {
+ return (
+ <GenerateOrderForm
+ blockchain={this.blockchain}
+ hashData={this.props.hashData}
+ dispatcher={this.props.dispatcher}
+ />
+ );
+ }
+ private onPortalDisclaimerAccepted() {
+ localStorage.setItem(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY, 'set');
+ this.setState({
+ hasAcceptedDisclaimer: true,
+ });
+ }
+ private getSharedOrderIfExists(): Order {
+ const queryString = window.location.search;
+ if (queryString.length === 0) {
+ return;
+ }
+ const queryParams = queryString.substring(1).split('&');
+ const orderQueryParam = _.find(queryParams, queryParam => {
+ const queryPair = queryParam.split('=');
+ return queryPair[0] === 'order';
+ });
+ if (_.isUndefined(orderQueryParam)) {
+ return;
+ }
+ const orderPair = orderQueryParam.split('=');
+ if (orderPair.length !== 2) {
+ return;
+ }
+
+ const validator = new SchemaValidator();
+ const order = JSON.parse(decodeURIComponent(orderPair[1]));
+ const validationResult = validator.validate(order, orderSchema);
+ if (validationResult.errors.length > 0) {
+ utils.consoleLog(`Invalid shared order: ${validationResult.errors}`);
+ return;
+ }
+ return order;
+ }
+ private updateScreenWidth() {
+ const newScreenWidth = utils.getScreenWidth();
+ this.props.dispatcher.updateScreenWidth(newScreenWidth);
+ }
+ private async updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) {
+ this.props.dispatcher.updateBlockchainIsLoaded(false);
+ await this.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
+ this.props.dispatcher.updateBlockchainIsLoaded(true);
+ }
+}
diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx
new file mode 100644
index 000000000..3b3641729
--- /dev/null
+++ b/packages/website/ts/components/portal_menu.tsx
@@ -0,0 +1,68 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {MenuItem} from 'ts/components/ui/menu_item';
+import {Link} from 'react-router-dom';
+import {WebsitePaths} from 'ts/types';
+
+export interface PortalMenuProps {
+ menuItemStyle: React.CSSProperties;
+ onClick?: () => void;
+}
+
+interface PortalMenuState {}
+
+export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState> {
+ public static defaultProps: Partial<PortalMenuProps> = {
+ onClick: _.noop,
+ };
+ public render() {
+ return (
+ <div>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this.renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/fill`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this.renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/balances`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this.renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/trades`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this.renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')}
+ </MenuItem>
+ </div>
+ );
+ }
+ private renderMenuItemWithIcon(title: string, iconName: string) {
+ return (
+ <div className="flex" style={{fontWeight: 100}}>
+ <div className="pr1 pl2">
+ <i style={{fontSize: 20}} className={`zmdi ${iconName}`} />
+ </div>
+ <div className="pl1">
+ {title}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx
new file mode 100644
index 000000000..274ba96a7
--- /dev/null
+++ b/packages/website/ts/components/send_button.tsx
@@ -0,0 +1,89 @@
+import * as _ from 'lodash';
+import {ZeroEx} from '0x.js';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import RaisedButton from 'material-ui/RaisedButton';
+import {BlockchainCallErrs, Token, TokenState} from 'ts/types';
+import {SendDialog} from 'ts/components/dialogs/send_dialog';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {Blockchain} from 'ts/blockchain';
+
+interface SendButtonProps {
+ token: Token;
+ tokenState: TokenState;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ onError: () => void;
+}
+
+interface SendButtonState {
+ isSendDialogVisible: boolean;
+ isSending: boolean;
+}
+
+export class SendButton extends React.Component<SendButtonProps, SendButtonState> {
+ public constructor(props: SendButtonProps) {
+ super(props);
+ this.state = {
+ isSendDialogVisible: false,
+ isSending: false,
+ };
+ }
+ public render() {
+ const labelStyle = this.state.isSending ? {fontSize: 10} : {};
+ return (
+ <div>
+ <RaisedButton
+ style={{width: '100%'}}
+ labelStyle={labelStyle}
+ disabled={this.state.isSending}
+ label={this.state.isSending ? 'Sending...' : 'Send'}
+ onClick={this.toggleSendDialog.bind(this)}
+ />
+ <SendDialog
+ isOpen={this.state.isSendDialogVisible}
+ onComplete={this.onSendAmountSelectedAsync.bind(this)}
+ onCancelled={this.toggleSendDialog.bind(this)}
+ token={this.props.token}
+ tokenState={this.props.tokenState}
+ />
+ </div>
+ );
+ }
+ private toggleSendDialog() {
+ this.setState({
+ isSendDialogVisible: !this.state.isSendDialogVisible,
+ });
+ }
+ private async onSendAmountSelectedAsync(recipient: string, value: BigNumber) {
+ this.setState({
+ isSending: true,
+ });
+ this.toggleSendDialog();
+ const token = this.props.token;
+ const tokenState = this.props.tokenState;
+ let balance = tokenState.balance;
+ try {
+ await this.props.blockchain.transferAsync(token, recipient, value);
+ balance = balance.minus(value);
+ this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ } catch (err) {
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ } else if (!_.includes(errMsg, 'User denied transaction')) {
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ this.props.onError();
+ }
+ }
+ this.setState({
+ isSending: false,
+ });
+ }
+}
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
new file mode 100644
index 000000000..c552d19dc
--- /dev/null
+++ b/packages/website/ts/components/token_balances.tsx
@@ -0,0 +1,697 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {ZeroEx} from '0x.js';
+import DharmaLoanFrame from 'dharma-loan-frame';
+import {colors} from 'material-ui/styles';
+import Dialog from 'material-ui/Dialog';
+import Divider from 'material-ui/Divider';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import FloatingActionButton from 'material-ui/FloatingActionButton';
+import ContentAdd from 'material-ui/svg-icons/content/add';
+import ContentRemove from 'material-ui/svg-icons/content/remove';
+import {
+ Table,
+ TableBody,
+ TableHeader,
+ TableRow,
+ TableHeaderColumn,
+ TableRowColumn,
+} from 'material-ui/Table';
+import ReactTooltip = require('react-tooltip');
+import BigNumber from 'bignumber.js';
+import firstBy = require('thenby');
+import QueryString = require('query-string');
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ TokenByAddress,
+ TokenStateByAddress,
+ Token,
+ BlockchainErrs,
+ BalanceErrs,
+ Styles,
+ ScreenWidths,
+ EtherscanLinkSuffixes,
+ BlockchainCallErrs,
+ TokenVisibility,
+} from 'ts/types';
+import {Blockchain} from 'ts/blockchain';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button';
+import {HelpTooltip} from 'ts/components/ui/help_tooltip';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {AllowanceToggle} from 'ts/components/inputs/allowance_toggle';
+import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button';
+import {SendButton} from 'ts/components/send_button';
+import {AssetPicker} from 'ts/components/generate_order/asset_picker';
+import {TokenIcon} from 'ts/components/ui/token_icon';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+
+const ETHER_ICON_PATH = '/images/ether.png';
+const ETHER_TOKEN_SYMBOL = 'WETH';
+const ZRX_TOKEN_SYMBOL = 'ZRX';
+
+const PRECISION = 5;
+const ICON_DIMENSION = 40;
+const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000;
+const TOKEN_TABLE_ROW_HEIGHT = 60;
+const MAX_TOKEN_TABLE_HEIGHT = 420;
+const TOKEN_COL_SPAN_LG = 2;
+const TOKEN_COL_SPAN_SM = 1;
+
+const styles: Styles = {
+ bgColor: {
+ backgroundColor: colors.grey50,
+ },
+};
+
+interface TokenBalancesProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userAddress: string;
+ userEtherBalance: BigNumber;
+ networkId: number;
+}
+
+interface TokenBalancesState {
+ errorType: BalanceErrs;
+ isBalanceSpinnerVisible: boolean;
+ isDharmaDialogVisible: boolean;
+ isZRXSpinnerVisible: boolean;
+ currentZrxBalance?: BigNumber;
+ isTokenPickerOpen: boolean;
+ isAddingToken: boolean;
+}
+
+export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
+ public constructor(props: TokenBalancesProps) {
+ super(props);
+ this.state = {
+ errorType: undefined,
+ isBalanceSpinnerVisible: false,
+ isZRXSpinnerVisible: false,
+ isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
+ isTokenPickerOpen: false,
+ isAddingToken: false,
+ };
+ }
+ public componentWillReceiveProps(nextProps: TokenBalancesProps) {
+ if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
+ if (this.state.isBalanceSpinnerVisible) {
+ const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance);
+ this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`);
+ }
+ this.setState({
+ isBalanceSpinnerVisible: false,
+ });
+ }
+ const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
+ if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
+ if (this.state.isZRXSpinnerVisible) {
+ const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
+ const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, 18);
+ this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
+ }
+ this.setState({
+ isZRXSpinnerVisible: false,
+ currentZrxBalance: undefined,
+ });
+ }
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ const errorDialogActions = [
+ <FlatButton
+ label="Ok"
+ primary={true}
+ onTouchTap={this.onErrorDialogToggle.bind(this, false)}
+ />,
+ ];
+ const dharmaDialogActions = [
+ <FlatButton
+ label="Close"
+ primary={true}
+ onTouchTap={this.onDharmaDialogToggle.bind(this, false)}
+ />,
+ ];
+ const isTestNetwork = this.props.networkId === constants.TESTNET_NETWORK_ID;
+ const dharmaButtonColumnStyle = {
+ paddingLeft: 3,
+ display: isTestNetwork ? 'table-cell' : 'none',
+ };
+ const stubColumnStyle = {
+ display: isTestNetwork ? 'none' : 'table-cell',
+ };
+ const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT;
+ const tokenTableHeight = allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ?
+ allTokenRowHeight :
+ MAX_TOKEN_TABLE_HEIGHT;
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.SM;
+ const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
+ const dharmaLoanExplanation = 'If you need access to larger amounts of ether,<br> \
+ you can request a loan from the Dharma Loan<br> \
+ network. Your loan should be funded in 5<br> \
+ minutes or less.';
+ const allowanceExplanation = '0x smart contracts require access to your<br> \
+ token balances in order to execute trades.<br> \
+ Toggling permissions sets an allowance for the<br> \
+ smart contract so you can start trading that token.';
+ return (
+ <div className="lg-px4 md-px4 sm-px1 pb2">
+ <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3>
+ <Divider />
+ <div className="pt2 pb2">
+ {isTestNetwork ?
+ 'In order to try out the 0x Portal Dapp, request some test ether to pay for \
+ gas costs. It might take a bit of time for the test ether to show up.' :
+ 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \
+ You can convert between Ether and Ether Tokens by clicking the "convert" button below.'
+ }
+ </div>
+ <Table
+ selectable={false}
+ style={styles.bgColor}
+ >
+ <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
+ <TableRow>
+ <TableHeaderColumn>Currency</TableHeaderColumn>
+ <TableHeaderColumn>Balance</TableHeaderColumn>
+ <TableRowColumn
+ className="sm-hide xs-hide"
+ style={stubColumnStyle}
+ />
+ {
+ isTestNetwork &&
+ <TableHeaderColumn
+ style={{paddingLeft: 3}}
+ >
+ {isSmallScreen ? 'Faucet' : 'Request from faucet'}
+ </TableHeaderColumn>
+ }
+ {
+ isTestNetwork &&
+ <TableHeaderColumn
+ style={dharmaButtonColumnStyle}
+ >
+ {isSmallScreen ? 'Loan' : 'Request Dharma loan'}
+ <HelpTooltip
+ style={{paddingLeft: 4}}
+ explanation={dharmaLoanExplanation}
+ />
+ </TableHeaderColumn>
+ }
+ </TableRow>
+ </TableHeader>
+ <TableBody displayRowCheckbox={false}>
+ <TableRow key="ETH">
+ <TableRowColumn className="py1">
+ <img
+ style={{width: ICON_DIMENSION, height: ICON_DIMENSION}}
+ src={ETHER_ICON_PATH}
+ />
+ </TableRowColumn>
+ <TableRowColumn>
+ {this.props.userEtherBalance.toFixed(PRECISION)} ETH
+ {this.state.isBalanceSpinnerVisible &&
+ <span className="pl1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ }
+ </TableRowColumn>
+ <TableRowColumn
+ className="sm-hide xs-hide"
+ style={stubColumnStyle}
+ />
+ {
+ isTestNetwork &&
+ <TableRowColumn style={{paddingLeft: 3}}>
+ <LifeCycleRaisedButton
+ labelReady="Request"
+ labelLoading="Sending..."
+ labelComplete="Sent!"
+ onClickAsyncFn={this.faucetRequestAsync.bind(this, true)}
+ />
+ </TableRowColumn>
+ }
+ {
+ isTestNetwork &&
+ <TableRowColumn style={dharmaButtonColumnStyle}>
+ <RaisedButton
+ label="Request"
+ style={{width: '100%'}}
+ onTouchTap={this.onDharmaDialogToggle.bind(this)}
+ />
+ </TableRowColumn>
+ }
+ </TableRow>
+ </TableBody>
+ </Table>
+ <div className="clearfix" style={{paddingBottom: 1}}>
+ <div className="col col-10">
+ <h3 className="pt2">
+ {isTestNetwork ? 'Test tokens' : 'Tokens'}
+ </h3>
+ </div>
+ <div className="col col-1 pt3 align-right">
+ <FloatingActionButton
+ mini={true}
+ zDepth={0}
+ onClick={this.onAddTokenClicked.bind(this)}
+ >
+ <ContentAdd />
+ </FloatingActionButton>
+ </div>
+ <div className="col col-1 pt3 align-right">
+ <FloatingActionButton
+ mini={true}
+ zDepth={0}
+ onClick={this.onRemoveTokenClicked.bind(this)}
+ >
+ <ContentRemove />
+ </FloatingActionButton>
+ </div>
+ </div>
+ <Divider />
+ <div className="pt2 pb2">
+ {isTestNetwork ?
+ 'Mint some test tokens you\'d like to use to generate or fill an order using 0x.' :
+ 'Set trading permissions for a token you\'d like to start trading.'
+ }
+ </div>
+ <Table
+ selectable={false}
+ bodyStyle={{height: tokenTableHeight}}
+ style={styles.bgColor}
+ >
+ <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
+ <TableRow>
+ <TableHeaderColumn
+ colSpan={tokenColSpan}
+ >
+ Token
+ </TableHeaderColumn>
+ <TableHeaderColumn style={{paddingLeft: 3}}>Balance</TableHeaderColumn>
+ <TableHeaderColumn>
+ <div className="inline-block">{!isSmallScreen && 'Trade '}Permissions</div>
+ <HelpTooltip
+ style={{paddingLeft: 4}}
+ explanation={allowanceExplanation}
+ />
+ </TableHeaderColumn>
+ <TableHeaderColumn>
+ Action
+ </TableHeaderColumn>
+ {this.props.screenWidth !== ScreenWidths.SM &&
+ <TableHeaderColumn>
+ Send
+ </TableHeaderColumn>
+ }
+ </TableRow>
+ </TableHeader>
+ <TableBody displayRowCheckbox={false}>
+ {this.renderTokenTableRows()}
+ </TableBody>
+ </Table>
+ <Dialog
+ title="Oh oh"
+ titleStyle={{fontWeight: 100}}
+ actions={errorDialogActions}
+ open={!_.isUndefined(this.state.errorType)}
+ onRequestClose={this.onErrorDialogToggle.bind(this, false)}
+ >
+ {this.renderErrorDialogBody()}
+ </Dialog>
+ <Dialog
+ title="Request Dharma Loan"
+ titleStyle={{fontWeight: 100, backgroundColor: 'rgb(250, 250, 250)'}}
+ bodyStyle={{backgroundColor: 'rgb(37, 37, 37)'}}
+ actionsContainerStyle={{backgroundColor: 'rgb(250, 250, 250)'}}
+ autoScrollBodyContent={true}
+ actions={dharmaDialogActions}
+ open={this.state.isDharmaDialogVisible}
+ >
+ {this.renderDharmaLoanFrame()}
+ </Dialog>
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isTokenPickerOpen}
+ currentTokenAddress={''}
+ onTokenChosen={this.onAssetTokenPicked.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED}
+ />
+ </div>
+ );
+ }
+ private renderTokenTableRows() {
+ if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== '') {
+ return '';
+ }
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.SM;
+ const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
+ const actionPaddingX = isSmallScreen ? 2 : 24;
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ const trackedTokensStartingWithEtherToken = trackedTokens.sort(
+ firstBy((t: Token) => (t.symbol !== ETHER_TOKEN_SYMBOL))
+ .thenBy((t: Token) => (t.symbol !== ZRX_TOKEN_SYMBOL))
+ .thenBy('address'),
+ );
+ const tableRows = _.map(
+ trackedTokensStartingWithEtherToken,
+ this.renderTokenRow.bind(this, tokenColSpan, actionPaddingX),
+ );
+ return tableRows;
+ }
+ private renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) {
+ const tokenState = this.props.tokenStateByAddress[token.address];
+ const tokenLink = utils.getEtherScanLinkIfExists(token.address, this.props.networkId,
+ EtherscanLinkSuffixes.address);
+ const isMintable = _.includes(configs.symbolsOfMintableTokens, token.symbol) &&
+ this.props.networkId !== constants.MAINNET_NETWORK_ID;
+ return (
+ <TableRow key={token.address} style={{height: TOKEN_TABLE_ROW_HEIGHT}}>
+ <TableRowColumn
+ colSpan={tokenColSpan}
+ >
+ {_.isUndefined(tokenLink) ?
+ this.renderTokenName(token) :
+ <a href={tokenLink} target="_blank" style={{textDecoration: 'none'}}>
+ {this.renderTokenName(token)}
+ </a>
+ }
+ </TableRowColumn>
+ <TableRowColumn style={{paddingRight: 3, paddingLeft: 3}}>
+ {this.renderAmount(tokenState.balance, token.decimals)} {token.symbol}
+ {this.state.isZRXSpinnerVisible && token.symbol === ZRX_TOKEN_SYMBOL &&
+ <span className="pl1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ }
+ </TableRowColumn>
+ <TableRowColumn>
+ <AllowanceToggle
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onErrorOccurred={this.onErrorOccurred.bind(this)}
+ userAddress={this.props.userAddress}
+ />
+ </TableRowColumn>
+ <TableRowColumn
+ style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}}
+ >
+ {isMintable &&
+ <LifeCycleRaisedButton
+ labelReady="Mint"
+ labelLoading={<span style={{fontSize: 12}}>Minting...</span>}
+ labelComplete="Minted!"
+ onClickAsyncFn={this.onMintTestTokensAsync.bind(this, token)}
+ />
+ }
+ {token.symbol === ETHER_TOKEN_SYMBOL &&
+ <EthWethConversionButton
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ ethToken={this.getWrappedEthToken()}
+ ethTokenState={tokenState}
+ userEtherBalance={this.props.userEtherBalance}
+ onError={this.onEthWethConversionFailed.bind(this)}
+ />
+ }
+ {token.symbol === ZRX_TOKEN_SYMBOL && this.props.networkId === constants.TESTNET_NETWORK_ID &&
+ <LifeCycleRaisedButton
+ labelReady="Request"
+ labelLoading="Sending..."
+ labelComplete="Sent!"
+ onClickAsyncFn={this.faucetRequestAsync.bind(this, false)}
+ />
+ }
+ </TableRowColumn>
+ {this.props.screenWidth !== ScreenWidths.SM &&
+ <TableRowColumn
+ style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}}
+ >
+ <SendButton
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onError={this.onSendFailed.bind(this)}
+ />
+ </TableRowColumn>
+ }
+ </TableRow>
+ );
+ }
+ private onAssetTokenPicked(tokenAddress: string) {
+ if (_.isEmpty(tokenAddress)) {
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ return;
+ }
+ const token = this.props.tokenByAddress[tokenAddress];
+ const isDefaultTrackedToken = _.includes(configs.defaultTrackedTokenSymbols, token.symbol);
+ if (!this.state.isAddingToken && !isDefaultTrackedToken) {
+ if (token.isRegistered) {
+ // Remove the token from tracked tokens
+ const newToken = _.assign({}, token, {
+ isTracked: false,
+ });
+ this.props.dispatcher.updateTokenByAddress([newToken]);
+ } else {
+ this.props.dispatcher.removeTokenToTokenByAddress(token);
+ }
+ this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
+ trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
+ } else if (isDefaultTrackedToken) {
+ this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
+ }
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ }
+ private onEthWethConversionFailed() {
+ this.setState({
+ errorType: BalanceErrs.wethConversionFailed,
+ });
+ }
+ private onSendFailed() {
+ this.setState({
+ errorType: BalanceErrs.sendFailed,
+ });
+ }
+ private renderAmount(amount: BigNumber, decimals: number) {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ return unitAmount.toNumber().toFixed(PRECISION);
+ }
+ private renderTokenName(token: Token) {
+ const tooltipId = `tooltip-${token.address}`;
+ return (
+ <div className="flex">
+ <TokenIcon token={token} diameter={ICON_DIMENSION} />
+ <div
+ data-tip={true}
+ data-for={tooltipId}
+ className="mt2 ml2 sm-hide xs-hide"
+ >
+ {token.name}
+ </div>
+ <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip>
+ </div>
+ );
+ }
+ private renderErrorDialogBody() {
+ switch (this.state.errorType) {
+ case BalanceErrs.incorrectNetworkForFaucet:
+ return (
+ <div>
+ Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME}
+ {' '}testnet (networkId {constants.TESTNET_NETWORK_ID}). Please make sure you are
+ {' '}connected to the {constants.TESTNET_NAME} testnet and try requesting ether again.
+ </div>
+ );
+
+ case BalanceErrs.faucetRequestFailed:
+ return (
+ <div>
+ An unexpected error occurred while trying to request test Ether from our faucet.
+ {' '}Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.faucetQueueIsFull:
+ return (
+ <div>
+ Our test Ether faucet queue is full. Please try requesting test Ether again later.
+ </div>
+ );
+
+ case BalanceErrs.mintingFailed:
+ return (
+ <div>
+ Minting your test tokens failed unexpectedly. Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.wethConversionFailed:
+ return (
+ <div>
+ Converting between Ether and Ether Tokens failed unexpectedly.
+ Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.allowanceSettingFailed:
+ return (
+ <div>
+ An unexpected error occurred while trying to set your test token allowance.
+ {' '}Please refresh the page and try again.
+ </div>
+ );
+
+ case undefined:
+ return null; // No error to show
+
+ default:
+ throw utils.spawnSwitchErr('errorType', this.state.errorType);
+ }
+ }
+ private renderDharmaLoanFrame() {
+ if (utils.isUserOnMobile()) {
+ return (
+ <h4 style={{ textAlign: 'center' }}>
+ We apologize -- Dharma loan requests are not available on
+ mobile yet. Please try again through your desktop browser.
+ </h4>
+ );
+ } else {
+ return (
+ <DharmaLoanFrame
+ partner="0x"
+ env={utils.getCurrentEnvironment()}
+ screenWidth={this.props.screenWidth}
+ />
+ );
+ }
+ }
+ private onErrorOccurred(errorType: BalanceErrs) {
+ this.setState({
+ errorType,
+ });
+ }
+ private async onMintTestTokensAsync(token: Token): Promise<boolean> {
+ try {
+ await this.props.blockchain.mintTestTokensAsync(token);
+ const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals);
+ this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`);
+ return true;
+ } catch (err) {
+ const errMsg = '' + err;
+ if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+ if (_.includes(errMsg, 'User denied transaction')) {
+ return false;
+ }
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ this.setState({
+ errorType: BalanceErrs.mintingFailed,
+ });
+ return false;
+ }
+ }
+ private async faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> {
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+
+ // If on another network other then the testnet our faucet serves test ether
+ // from, we must show user an error message
+ if (this.props.blockchain.networkId !== constants.TESTNET_NETWORK_ID) {
+ this.setState({
+ errorType: BalanceErrs.incorrectNetworkForFaucet,
+ });
+ return false;
+ }
+
+ await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY);
+
+ const segment = isEtherRequest ? 'ether' : 'zrx';
+ const response = await fetch(`${constants.ETHER_FAUCET_ENDPOINT}/${segment}/${this.props.userAddress}`);
+ const responseBody = await response.text();
+ if (response.status !== constants.SUCCESS_STATUS) {
+ utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`);
+ await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`));
+ const errorType = response.status === constants.UNAVAILABLE_STATUS ?
+ BalanceErrs.faucetQueueIsFull :
+ BalanceErrs.faucetRequestFailed;
+ this.setState({
+ errorType,
+ });
+ return false;
+ }
+
+ if (isEtherRequest) {
+ this.setState({
+ isBalanceSpinnerVisible: true,
+ });
+ } else {
+ const tokens = _.values(this.props.tokenByAddress);
+ const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
+ this.setState({
+ isZRXSpinnerVisible: true,
+ currentZrxBalance: zrxTokenState.balance,
+ });
+ this.props.blockchain.pollTokenBalanceAsync(zrxToken);
+ }
+ return true;
+ }
+ private onErrorDialogToggle(isOpen: boolean) {
+ this.setState({
+ errorType: undefined,
+ });
+ }
+ private onDharmaDialogToggle() {
+ this.setState({
+ isDharmaDialogVisible: !this.state.isDharmaDialogVisible,
+ });
+ }
+ private getWrappedEthToken() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const wrappedEthToken = _.find(tokens, {symbol: ETHER_TOKEN_SYMBOL});
+ return wrappedEthToken;
+ }
+ private onAddTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: true,
+ });
+ }
+ private onRemoveTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: false,
+ });
+ }
+}
diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx
new file mode 100644
index 000000000..6248095b3
--- /dev/null
+++ b/packages/website/ts/components/top_bar.tsx
@@ -0,0 +1,370 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {
+ Link as ScrollLink,
+ animateScroll,
+} from 'react-scroll';
+import {Link} from 'react-router-dom';
+import {HashLink} from 'react-router-hash-link';
+import AppBar from 'material-ui/AppBar';
+import Drawer from 'material-ui/Drawer';
+import MenuItem from 'material-ui/MenuItem';
+import {colors} from 'material-ui/styles';
+import ReactTooltip = require('react-tooltip');
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {Identicon} from 'ts/components/ui/identicon';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+import {PortalMenu} from 'ts/components/portal_menu';
+import {Styles, TypeDocNode, MenuSubsectionsBySection, WebsitePaths, Docs} from 'ts/types';
+import {TopBarMenuItem} from 'ts/components/top_bar_menu_item';
+import {DropDownMenuItem} from 'ts/components/ui/drop_down_menu_item';
+
+const CUSTOM_DARK_GRAY = '#231F20';
+const SECTION_HEADER_COLOR = 'rgb(234, 234, 234)';
+
+interface TopBarProps {
+ userAddress?: string;
+ blockchainIsLoaded: boolean;
+ location: Location;
+ docsVersion?: string;
+ availableDocVersions?: string[];
+ menuSubsectionsBySection?: MenuSubsectionsBySection;
+ shouldFullWidth?: boolean;
+ doc?: Docs;
+ style?: React.CSSProperties;
+ isNightVersion?: boolean;
+}
+
+interface TopBarState {
+ isDrawerOpen: boolean;
+}
+
+const styles: Styles = {
+ address: {
+ marginRight: 12,
+ overflow: 'hidden',
+ paddingTop: 4,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ width: 70,
+ },
+ addressPopover: {
+ backgroundColor: colors.blueGrey500,
+ color: 'white',
+ padding: 3,
+ },
+ topBar: {
+ backgroundColor: 'white',
+ height: 59,
+ width: '100%',
+ position: 'fixed',
+ top: 0,
+ zIndex: 1100,
+ paddingBottom: 1,
+ },
+ bottomBar: {
+ boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px',
+ },
+ menuItem: {
+ fontSize: 14,
+ color: CUSTOM_DARK_GRAY,
+ paddingTop: 6,
+ paddingBottom: 6,
+ marginTop: 17,
+ cursor: 'pointer',
+ fontWeight: 400,
+ },
+};
+
+export class TopBar extends React.Component<TopBarProps, TopBarState> {
+ public static defaultProps: Partial<TopBarProps> = {
+ shouldFullWidth: false,
+ style: {},
+ isNightVersion: false,
+ };
+ constructor(props: TopBarProps) {
+ super(props);
+ this.state = {
+ isDrawerOpen: false,
+ };
+ }
+ public render() {
+ const isNightVersion = this.props.isNightVersion;
+ const isFullWidthPage = this.props.shouldFullWidth;
+ const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
+ const developerSectionMenuItems = [
+ <Link
+ key="subMenuItem-zeroEx"
+ to={WebsitePaths.ZeroExJs}
+ className="text-decoration-none"
+ >
+ <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="0x.js" />
+ </Link>,
+ <Link
+ key="subMenuItem-smartContracts"
+ to={WebsitePaths.SmartContracts}
+ className="text-decoration-none"
+ >
+ <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Smart Contracts" />
+ </Link>,
+ <a
+ key="subMenuItem-standard-relayer-api"
+ target="_blank"
+ className="text-decoration-none"
+ href={constants.STANDARD_RELAYER_API_GITHUB}
+ >
+ <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Standard Relayer API" />
+ </a>,
+ <a
+ key="subMenuItem-github"
+ target="_blank"
+ className="text-decoration-none"
+ href={constants.GITHUB_URL}
+ >
+ <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" />
+ </a>,
+ <a
+ key="subMenuItem-whitePaper"
+ target="_blank"
+ className="text-decoration-none"
+ href={`${WebsitePaths.Whitepaper}`}
+ >
+ <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Whitepaper" />
+ </a>,
+ ];
+ const bottomBorderStyle = this.shouldDisplayBottomBar() ? styles.bottomBar : {};
+ const fullWithClassNames = isFullWidthPage ? 'pr4' : '';
+ const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
+ return (
+ <div style={{...styles.topBar, ...bottomBorderStyle, ...this.props.style}} className="pb1">
+ <div className={parentClassNames}>
+ <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{paddingTop: 15}}>
+ <Link to={`${WebsitePaths.Home}`} className="text-decoration-none">
+ <img src={logoUrl} height="30" />
+ </Link>
+ </div>
+ <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} />
+ <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} />
+ {!this.isViewingPortal() &&
+ <div className={`col col-${isFullWidthPage ? '4' : '5'} ${fullWithClassNames} lg-pr0 md-pr2 sm-hide xs-hide`}>
+ <div className="flex justify-between">
+ <DropDownMenuItem
+ title="Developers"
+ subMenuItems={developerSectionMenuItems}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="Wiki"
+ path={`${WebsitePaths.Wiki}`}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="About"
+ path={`${WebsitePaths.About}`}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="Portal DApp"
+ path={`${WebsitePaths.Portal}`}
+ isPrimary={true}
+ style={styles.menuItem}
+ className={`${isFullWidthPage && 'md-hide'}`}
+ isNightVersion={isNightVersion}
+ />
+ </div>
+ </div>
+ }
+ {this.props.blockchainIsLoaded && !_.isEmpty(this.props.userAddress) &&
+ <div className="col col-5">
+ {this.renderUser()}
+ </div>
+ }
+ {!this.isViewingPortal() &&
+ <div
+ className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}
+ >
+ <div
+ style={{fontSize: 25, color: isNightVersion ? 'white' : 'black', cursor: 'pointer', paddingTop: 16}}
+ >
+ <i
+ className="zmdi zmdi-menu"
+ onClick={this.onMenuButtonClick.bind(this)}
+ />
+ </div>
+ </div>
+ }
+ </div>
+ {this.renderDrawer()}
+ </div>
+ );
+ }
+ private renderDrawer() {
+ return (
+ <Drawer
+ open={this.state.isDrawerOpen}
+ docked={false}
+ openSecondary={true}
+ onRequestChange={this.onMenuButtonClick.bind(this)}
+ >
+ {this.renderPortalMenu()}
+ {this.renderDocsMenu()}
+ {this.renderWiki()}
+ <div className="pl1 py1 mt3" style={{backgroundColor: SECTION_HEADER_COLOR}}>Website</div>
+ <Link to={WebsitePaths.Home} className="text-decoration-none">
+ <MenuItem className="py2">Home</MenuItem>
+ </Link>
+ <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none">
+ <MenuItem className="py2">Wiki</MenuItem>
+ </Link>
+ {!this.isViewing0xjsDocs() &&
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <MenuItem className="py2">0x.js Docs</MenuItem>
+ </Link>
+ }
+ {!this.isViewingSmartContractsDocs() &&
+ <Link to={WebsitePaths.SmartContracts} className="text-decoration-none">
+ <MenuItem className="py2">Smart Contract Docs</MenuItem>
+ </Link>
+ }
+ {!this.isViewingPortal() &&
+ <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none">
+ <MenuItem className="py2">Portal DApp</MenuItem>
+ </Link>
+ }
+ <a
+ className="text-decoration-none"
+ target="_blank"
+ href={`${WebsitePaths.Whitepaper}`}
+ >
+ <MenuItem className="py2">Whitepaper</MenuItem>
+ </a>
+ <Link to={`${WebsitePaths.About}`} className="text-decoration-none">
+ <MenuItem className="py2">About</MenuItem>
+ </Link>
+ <a
+ className="text-decoration-none"
+ target="_blank"
+ href={constants.BLOG_URL}
+ >
+ <MenuItem className="py2">Blog</MenuItem>
+ </a>
+ <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none">
+ <MenuItem
+ className="py2"
+ onTouchTap={this.onMenuButtonClick.bind(this)}
+ >
+ FAQ
+ </MenuItem>
+ </Link>
+ </Drawer>
+ );
+ }
+ private renderDocsMenu() {
+ if (!this.isViewing0xjsDocs() && !this.isViewingSmartContractsDocs()) {
+ return;
+ }
+
+ const topLevelMenu = this.isViewing0xjsDocs() ?
+ typeDocUtils.getFinal0xjsMenu(this.props.docsVersion) :
+ constants.menuSmartContracts;
+
+ const sectionTitle = this.isViewing0xjsDocs() ? '0x.js Docs' : 'Smart contract Docs';
+ return (
+ <div className="lg-hide md-hide">
+ <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>{sectionTitle}</div>
+ <NestedSidebarMenu
+ topLevelMenu={topLevelMenu}
+ menuSubsectionsBySection={this.props.menuSubsectionsBySection}
+ shouldDisplaySectionHeaders={false}
+ onMenuItemClick={this.onMenuButtonClick.bind(this)}
+ selectedVersion={this.props.docsVersion}
+ doc={this.props.doc}
+ versions={this.props.availableDocVersions}
+ />
+ </div>
+ );
+ }
+ private renderWiki() {
+ if (!this.isViewingWiki()) {
+ return;
+ }
+
+ return (
+ <div className="lg-hide md-hide">
+ <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>0x Protocol Wiki</div>
+ <NestedSidebarMenu
+ topLevelMenu={this.props.menuSubsectionsBySection}
+ menuSubsectionsBySection={this.props.menuSubsectionsBySection}
+ shouldDisplaySectionHeaders={false}
+ onMenuItemClick={this.onMenuButtonClick.bind(this)}
+ />
+ </div>
+ );
+ }
+ private renderPortalMenu() {
+ if (!this.isViewingPortal()) {
+ return;
+ }
+
+ return (
+ <div className="lg-hide md-hide">
+ <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>Portal DApp</div>
+ <PortalMenu
+ menuItemStyle={{color: 'black'}}
+ onClick={this.onMenuButtonClick.bind(this)}
+ />
+ </div>
+ );
+ }
+ private renderUser() {
+ const userAddress = this.props.userAddress;
+ const identiconDiameter = 26;
+ return (
+ <div
+ className="flex right lg-pr0 md-pr2 sm-pr2"
+ style={{paddingTop: 16}}
+ >
+ <div
+ style={styles.address}
+ data-tip={true}
+ data-for="userAddressTooltip"
+ >
+ {!_.isEmpty(userAddress) ? userAddress : ''}
+ </div>
+ <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip>
+ <div>
+ <Identicon address={userAddress} diameter={identiconDiameter} />
+ </div>
+ </div>
+ );
+ }
+ private onMenuButtonClick() {
+ this.setState({
+ isDrawerOpen: !this.state.isDrawerOpen,
+ });
+ }
+ private isViewingPortal() {
+ return _.includes(this.props.location.pathname, WebsitePaths.Portal);
+ }
+ private isViewingFAQ() {
+ return _.includes(this.props.location.pathname, WebsitePaths.FAQ);
+ }
+ private isViewing0xjsDocs() {
+ return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs);
+ }
+ private isViewingSmartContractsDocs() {
+ return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts);
+ }
+ private isViewingWiki() {
+ return _.includes(this.props.location.pathname, WebsitePaths.Wiki);
+ }
+ private shouldDisplayBottomBar() {
+ return this.isViewingWiki() || this.isViewing0xjsDocs() || this.isViewingFAQ() ||
+ this.isViewingSmartContractsDocs();
+ }
+}
diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx
new file mode 100644
index 000000000..de429fba6
--- /dev/null
+++ b/packages/website/ts/components/top_bar_menu_item.tsx
@@ -0,0 +1,53 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Link} from 'react-router-dom';
+import {Styles} from 'ts/types';
+
+const CUSTOM_DARK_GRAY = '#231F20';
+const DEFAULT_STYLE = {
+ color: CUSTOM_DARK_GRAY,
+};
+
+interface TopBarMenuItemProps {
+ title: string;
+ path?: string;
+ isPrimary?: boolean;
+ style?: React.CSSProperties;
+ className?: string;
+ isNightVersion?: boolean;
+}
+
+interface TopBarMenuItemState {}
+
+export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarMenuItemState> {
+ public static defaultProps: Partial<TopBarMenuItemProps> = {
+ isPrimary: false,
+ style: DEFAULT_STYLE,
+ className: '',
+ isNightVersion: false,
+ };
+ public render() {
+ const primaryStyles = this.props.isPrimary ? {
+ borderRadius: 4,
+ border: `1px solid ${this.props.isNightVersion ? '#979797' : 'rgb(230, 229, 229)'}`,
+ marginTop: 15,
+ paddingLeft: 9,
+ paddingRight: 9,
+ width: 77,
+ } : {};
+ const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color;
+ const linkColor = _.isUndefined(menuItemColor) ?
+ CUSTOM_DARK_GRAY :
+ menuItemColor;
+ return (
+ <div
+ className={`center ${this.props.className}`}
+ style={{...this.props.style, ...primaryStyles, color: menuItemColor}}
+ >
+ <Link to={this.props.path} className="text-decoration-none" style={{color: linkColor}}>
+ {this.props.title}
+ </Link>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx
new file mode 100644
index 000000000..bc036eae3
--- /dev/null
+++ b/packages/website/ts/components/track_token_confirmation.tsx
@@ -0,0 +1,65 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import FlatButton from 'material-ui/FlatButton';
+import Dialog from 'material-ui/Dialog';
+import {utils} from 'ts/utils/utils';
+import {Party} from 'ts/components/ui/party';
+import {Token, TokenByAddress} from 'ts/types';
+
+interface TrackTokenConfirmationProps {
+ tokens: Token[];
+ tokenByAddress: TokenByAddress;
+ networkId: number;
+ isAddingTokenToTracked: boolean;
+}
+
+interface TrackTokenConfirmationState {}
+
+export class TrackTokenConfirmation extends
+ React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> {
+ public render() {
+ const isMultipleTokens = this.props.tokens.length > 1;
+ const allTokens = _.values(this.props.tokenByAddress);
+ return (
+ <div style={{color: colors.grey700}}>
+ {this.props.isAddingTokenToTracked ?
+ <div className="py4 my4 center">
+ <span className="pr1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ <span>Adding token{isMultipleTokens && 's'}...</span>
+ </div> :
+ <div>
+ <div>
+ You do not currently track the following token{isMultipleTokens && 's'}:
+ </div>
+ <div className="py2 clearfix mx-auto center" style={{width: 355}}>
+ {_.map(this.props.tokens, (token: Token) => (
+ <div
+ key={`token-profile-${token.name}`}
+ className={`col col-${isMultipleTokens ? '6' : '12'} px2`}
+ >
+ <Party
+ label={token.name}
+ address={token.address}
+ networkId={this.props.networkId}
+ alternativeImage={token.iconUrl}
+ isInTokenRegistry={token.isRegistered}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)}
+ />
+ </div>
+ ))}
+ </div>
+ <div>
+ Tracking a token adds it to the balances section of 0x Portal and
+ allows you to generate/fill orders involving the token
+ {isMultipleTokens && 's'}. Would you like to start tracking{' '}
+ {isMultipleTokens ? 'these' : 'this'}{' '}token?
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx
new file mode 100644
index 000000000..9deaf8fd8
--- /dev/null
+++ b/packages/website/ts/components/trade_history/trade_history.tsx
@@ -0,0 +1,115 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import Paper from 'material-ui/Paper';
+import Divider from 'material-ui/Divider';
+import {utils} from 'ts/utils/utils';
+import {Fill, TokenByAddress} from 'ts/types';
+import {TradeHistoryItem} from 'ts/components/trade_history/trade_history_item';
+import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage';
+
+const FILL_POLLING_INTERVAL = 1000;
+
+interface TradeHistoryProps {
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
+ networkId: number;
+}
+
+interface TradeHistoryState {
+ sortedFills: Fill[];
+}
+
+export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> {
+ private fillPollingIntervalId: number;
+ public constructor(props: TradeHistoryProps) {
+ super(props);
+ const sortedFills = this.getSortedFills();
+ this.state = {
+ sortedFills,
+ };
+ }
+ public componentWillMount() {
+ this.startPollingForFills();
+ }
+ public componentWillUnmount() {
+ this.stopPollingForFills();
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div className="lg-px4 md-px4 sm-px2">
+ <h3>Trade history</h3>
+ <Divider />
+ <div className="pt2" style={{height: 608, overflow: 'scroll'}}>
+ {this.renderTrades()}
+ </div>
+ </div>
+ );
+ }
+ private renderTrades() {
+ const numNonCustomFills = this.numFillsWithoutCustomERC20Tokens();
+ if (numNonCustomFills === 0) {
+ return this.renderEmptyNotice();
+ }
+
+ return _.map(this.state.sortedFills, (fill, index) => {
+ return (
+ <TradeHistoryItem
+ key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`}
+ fill={fill}
+ tokenByAddress={this.props.tokenByAddress}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ />
+ );
+ });
+ }
+ private renderEmptyNotice() {
+ return (
+ <Paper className="mt1 p2 mx-auto center" style={{width: '80%'}}>
+ No filled orders yet.
+ </Paper>
+ );
+ }
+ private numFillsWithoutCustomERC20Tokens() {
+ let numNonCustomFills = 0;
+ const tokens = _.values(this.props.tokenByAddress);
+ _.each(this.state.sortedFills, fill => {
+ const takerToken = _.find(tokens, token => {
+ return token.address === fill.takerToken;
+ });
+ const makerToken = _.find(tokens, token => {
+ return token.address === fill.makerToken;
+ });
+ // For now we don't show history items for orders using custom ERC20
+ // tokens the client does not know how to display.
+ // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
+ // Be sure to remove similar logic in trade_history_item.tsx
+ if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) {
+ numNonCustomFills += 1;
+ }
+ });
+ return numNonCustomFills;
+ }
+ private startPollingForFills() {
+ this.fillPollingIntervalId = window.setInterval(() => {
+ const sortedFills = this.getSortedFills();
+ if (!utils.deepEqual(sortedFills, this.state.sortedFills)) {
+ this.setState({
+ sortedFills,
+ });
+ }
+ }, FILL_POLLING_INTERVAL);
+ }
+ private stopPollingForFills() {
+ clearInterval(this.fillPollingIntervalId);
+ }
+ private getSortedFills() {
+ const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId);
+ const fills = _.values(fillsByHash);
+ const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]);
+ return sortedFills;
+ }
+}
diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx
new file mode 100644
index 000000000..96b755e3c
--- /dev/null
+++ b/packages/website/ts/components/trade_history/trade_history_item.tsx
@@ -0,0 +1,178 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import BigNumber from 'bignumber.js';
+import * as ReactTooltip from 'react-tooltip';
+import * as moment from 'moment';
+import Paper from 'material-ui/Paper';
+import {colors} from 'material-ui/styles';
+import {ZeroEx} from '0x.js';
+import {TokenByAddress, Fill, Token, EtherscanLinkSuffixes} from 'ts/types';
+import {Party} from 'ts/components/ui/party';
+import {EtherScanIcon} from 'ts/components/ui/etherscan_icon';
+
+const PRECISION = 5;
+const IDENTICON_DIAMETER = 40;
+
+interface TradeHistoryItemProps {
+ fill: Fill;
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
+ networkId: number;
+}
+
+interface TradeHistoryItemState {}
+
+export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, TradeHistoryItemState> {
+ public render() {
+ const fill = this.props.fill;
+ const tokens = _.values(this.props.tokenByAddress);
+ const takerToken = _.find(tokens, token => {
+ return token.address === fill.takerToken;
+ });
+ const makerToken = _.find(tokens, token => {
+ return token.address === fill.makerToken;
+ });
+ // For now we don't show history items for orders using custom ERC20
+ // tokens the client does not know how to display.
+ // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
+ // Be sure to remove similar logic in trade_history.tsx
+ if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) {
+ return null;
+ }
+
+ const amountColStyle: React.CSSProperties = {
+ fontWeight: 100,
+ display: 'inline-block',
+ };
+ const amountColClassNames = 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \
+ lg-right-align md-right-align sm-center';
+
+ return (
+ <Paper
+ className="py1"
+ style={{margin: '3px 3px 15px 3px'}}
+ >
+ <div className="clearfix">
+ <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">
+ {this.renderDate()}
+ </div>
+ <div
+ className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3"
+ style={{fontSize: 12, fontWeight: 100}}
+ >
+ <div className="flex sm-mx-auto xs-mx-auto" style={{paddingTop: 4, width: 224}}>
+ <Party
+ label="Maker"
+ address={fill.maker}
+ identiconDiameter={IDENTICON_DIAMETER}
+ networkId={this.props.networkId}
+ />
+ <i style={{fontSize: 30}} className="zmdi zmdi-swap py3" />
+ <Party
+ label="Taker"
+ address={fill.taker}
+ identiconDiameter={IDENTICON_DIAMETER}
+ networkId={this.props.networkId}
+ />
+ </div>
+ </div>
+ <div
+ className={amountColClassNames}
+ style={amountColStyle}
+ >
+ {this.renderAmounts(makerToken, takerToken)}
+ </div>
+ <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center">
+ <div className="pt1 lg-right md-right sm-mx-auto" style={{width: 13}}>
+ <EtherScanIcon
+ addressOrTxHash={fill.transactionHash}
+ networkId={this.props.networkId}
+ etherscanLinkSuffixes={EtherscanLinkSuffixes.tx}
+ />
+ </div>
+ </div>
+ </div>
+ </Paper>
+ );
+ }
+ private renderAmounts(makerToken: Token, takerToken: Token) {
+ const fill = this.props.fill;
+ const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals);
+ const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals);
+ let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits);
+ const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals);
+
+ let receiveAmount;
+ let receiveToken;
+ let givenAmount;
+ let givenToken;
+ if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) {
+ receiveAmount = new BigNumber(0);
+ givenAmount = new BigNumber(0);
+ receiveToken = makerToken;
+ givenToken = takerToken;
+ } else if (this.props.userAddress === fill.maker) {
+ receiveAmount = fill.filledTakerTokenAmount;
+ givenAmount = fillMakerTokenAmount;
+ receiveToken = takerToken;
+ givenToken = makerToken;
+ exchangeRate = new BigNumber(1).div(exchangeRate);
+ } else if (this.props.userAddress === fill.taker) {
+ receiveAmount = fillMakerTokenAmount;
+ givenAmount = fill.filledTakerTokenAmount;
+ receiveToken = makerToken;
+ givenToken = takerToken;
+ }
+
+ return (
+ <div>
+ <div
+ style={{color: colors.green400, fontSize: 16}}
+ >
+ <span>+{' '}</span>
+ {this.renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)}
+ </div>
+ <div
+ className="pb1 inline-block"
+ style={{color: colors.red200, fontSize: 16}}
+ >
+ <span>-{' '}</span>
+ {this.renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)}
+ </div>
+ <div style={{color: colors.grey400, fontSize: 14}}>
+ {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol}
+ </div>
+ </div>
+ );
+ }
+ private renderDate() {
+ const blockMoment = moment.unix(this.props.fill.blockTimestamp);
+ if (!blockMoment.isValid()) {
+ return null;
+ }
+
+ const dayOfMonth = blockMoment.format('D');
+ const monthAbreviation = blockMoment.format('MMM');
+ const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY');
+ const dateTooltipId = `${this.props.fill.transactionHash}-date`;
+
+ return (
+ <div
+ data-tip={true}
+ data-for={dateTooltipId}
+ >
+ <div className="center pt1" style={{fontSize: 13}}>{monthAbreviation}</div>
+ <div className="center" style={{fontSize: 24, fontWeight: 100}}>{dayOfMonth}</div>
+ <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip>
+ </div>
+ );
+ }
+ private renderAmount(amount: BigNumber, symbol: string, decimals: number) {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ return (
+ <span>
+ {unitAmount.toFixed(PRECISION)} {symbol}
+ </span>
+ );
+ }
+}
diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx
new file mode 100644
index 000000000..bf2f0baf5
--- /dev/null
+++ b/packages/website/ts/components/ui/alert.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {AlertTypes} from 'ts/types';
+
+const CUSTOM_GREEN = 'rgb(137, 199, 116)';
+
+interface AlertProps {
+ type: AlertTypes;
+ message: string|React.ReactNode;
+}
+
+export function Alert(props: AlertProps) {
+ const isAlert = props.type === AlertTypes.ERROR;
+ const errMsgStyles = {
+ background: isAlert ? colors.red200 : CUSTOM_GREEN,
+ color: 'white',
+ marginTop: 10,
+ padding: 4,
+ paddingLeft: 8,
+ };
+
+ return (
+ <div className="rounded center" style={errMsgStyles}>
+ {props.message}
+ </div>
+ );
+}
diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx
new file mode 100644
index 000000000..1e3bbdb99
--- /dev/null
+++ b/packages/website/ts/components/ui/badge.tsx
@@ -0,0 +1,58 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {Styles} from 'ts/types';
+
+const styles: Styles = {
+ badge: {
+ width: 50,
+ fontSize: 11,
+ height: 10,
+ borderRadius: 5,
+ marginTop: 25,
+ lineHeight: 0.9,
+ fontFamily: 'Roboto Mono',
+ marginLeft: 3,
+ marginRight: 3,
+ },
+};
+
+interface BadgeProps {
+ title: string;
+ backgroundColor: string;
+}
+
+interface BadgeState {
+ isHovering: boolean;
+}
+
+export class Badge extends React.Component<BadgeProps, BadgeState> {
+ constructor(props: BadgeProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const badgeStyle = {
+ ...styles.badge,
+ backgroundColor: this.props.backgroundColor,
+ opacity: this.state.isHovering ? 0.7 : 1,
+ };
+ return (
+ <div
+ className="p1 center"
+ style={badgeStyle}
+ onMouseOver={this.setHoverState.bind(this, true)}
+ onMouseOut={this.setHoverState.bind(this, false)}
+ >
+ {this.props.title}
+ </div>
+ );
+ }
+ private setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx
new file mode 100644
index 000000000..f8abaa59e
--- /dev/null
+++ b/packages/website/ts/components/ui/copy_icon.tsx
@@ -0,0 +1,81 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import * as CopyToClipboard from 'react-copy-to-clipboard';
+import {colors} from 'material-ui/styles';
+import ReactTooltip = require('react-tooltip');
+
+interface CopyIconProps {
+ data: string;
+ callToAction?: string;
+}
+
+interface CopyIconState {
+ isHovering: boolean;
+}
+
+export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> {
+ private copyTooltipTimeoutId: number;
+ private copyable: HTMLInputElement;
+ constructor(props: CopyIconProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public componentDidUpdate() {
+ // Remove tooltip if hover away
+ if (!this.state.isHovering && this.copyTooltipTimeoutId) {
+ clearInterval(this.copyTooltipTimeoutId);
+ this.hideTooltip();
+ }
+ }
+ public render() {
+ return (
+ <div className="inline-block">
+ <CopyToClipboard text={this.props.data} onCopy={this.onCopy.bind(this)}>
+ <div
+ className="inline flex"
+ style={{cursor: 'pointer', color: colors.amber600}}
+ ref={this.setRefToProperty.bind(this)}
+ data-tip={true}
+ data-for="copy"
+ data-event="click"
+ data-iscapture={true} // This let's the click event continue to propogate
+ onMouseOver={this.setHoverState.bind(this, true)}
+ onMouseOut={this.setHoverState.bind(this, false)}
+ >
+ <div>
+ <i style={{fontSize: 15}} className="zmdi zmdi-copy" />
+ </div>
+ {this.props.callToAction &&
+ <div className="pl1">{this.props.callToAction}</div>
+ }
+ </div>
+ </CopyToClipboard>
+ <ReactTooltip id="copy">Copied!</ReactTooltip>
+ </div>
+ );
+ }
+ private setRefToProperty(el: HTMLInputElement) {
+ this.copyable = el;
+ }
+ private setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+ private onCopy() {
+ if (this.copyTooltipTimeoutId) {
+ clearInterval(this.copyTooltipTimeoutId);
+ }
+
+ const tooltipLifespanMs = 1000;
+ this.copyTooltipTimeoutId = window.setTimeout(() => {
+ this.hideTooltip();
+ }, tooltipLifespanMs);
+ }
+ private hideTooltip() {
+ ReactTooltip.hide(ReactDOM.findDOMNode(this.copyable));
+ }
+}
diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx
new file mode 100644
index 000000000..b8b7eb167
--- /dev/null
+++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx
@@ -0,0 +1,117 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {
+ Link as ScrollLink,
+} from 'react-scroll';
+import {Link} from 'react-router-dom';
+import Popover from 'material-ui/Popover';
+import Menu from 'material-ui/Menu';
+import MenuItem from 'material-ui/MenuItem';
+import {Styles, WebsitePaths} from 'ts/types';
+
+const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
+const CUSTOM_LIGHT_GRAY = '#848484';
+const DEFAULT_STYLE = {
+ fontSize: 14,
+};
+
+interface DropDownMenuItemProps {
+ title: string;
+ subMenuItems: React.ReactNode[];
+ style?: React.CSSProperties;
+ menuItemStyle?: React.CSSProperties;
+ isNightVersion?: boolean;
+}
+
+interface DropDownMenuItemState {
+ isDropDownOpen: boolean;
+ anchorEl?: HTMLInputElement;
+}
+
+export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> {
+ public static defaultProps: Partial<DropDownMenuItemProps> = {
+ style: DEFAULT_STYLE,
+ menuItemStyle: DEFAULT_STYLE,
+ isNightVersion: false,
+ };
+ private isHovering: boolean;
+ private popoverCloseCheckIntervalId: number;
+ constructor(props: DropDownMenuItemProps) {
+ super(props);
+ this.state = {
+ isDropDownOpen: false,
+ };
+ }
+ public componentDidMount() {
+ this.popoverCloseCheckIntervalId = window.setInterval(() => {
+ this.checkIfShouldClosePopover();
+ }, CHECK_CLOSE_POPOVER_INTERVAL_MS);
+ }
+ public componentWillUnmount() {
+ window.clearInterval(this.popoverCloseCheckIntervalId);
+ }
+ public render() {
+ const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
+ return (
+ <div
+ style={{...this.props.style, color: colorStyle}}
+ onMouseEnter={this.onHover.bind(this)}
+ onMouseLeave={this.onHoverOff.bind(this)}
+ >
+ <div className="flex relative">
+ <div style={{paddingRight: 10}}>
+ {this.props.title}
+ </div>
+ <div className="absolute" style={{paddingLeft: 3, right: 3, top: -2}}>
+ <i className="zmdi zmdi-caret-right" style={{fontSize: 22}} />
+ </div>
+ </div>
+ <Popover
+ open={this.state.isDropDownOpen}
+ anchorEl={this.state.anchorEl}
+ anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}}
+ targetOrigin={{horizontal: 'middle', vertical: 'top'}}
+ onRequestClose={this.closePopover.bind(this)}
+ useLayerForClickAway={false}
+ >
+ <div
+ onMouseEnter={this.onHover.bind(this)}
+ onMouseLeave={this.onHoverOff.bind(this)}
+ >
+ <Menu style={{color: CUSTOM_LIGHT_GRAY}}>
+ {this.props.subMenuItems}
+ </Menu>
+ </div>
+ </Popover>
+ </div>
+ );
+ }
+ private onHover(event: React.FormEvent<HTMLInputElement>) {
+ this.isHovering = true;
+ this.checkIfShouldOpenPopover(event);
+ }
+ private checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) {
+ if (this.state.isDropDownOpen) {
+ return; // noop
+ }
+
+ this.setState({
+ isDropDownOpen: true,
+ anchorEl: event.currentTarget,
+ });
+ }
+ private onHoverOff(event: React.FormEvent<HTMLInputElement>) {
+ this.isHovering = false;
+ }
+ private checkIfShouldClosePopover() {
+ if (!this.state.isDropDownOpen || this.isHovering) {
+ return; // noop
+ }
+ this.closePopover();
+ }
+ private closePopover() {
+ this.setState({
+ isDropDownOpen: false,
+ });
+ }
+}
diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx
new file mode 100644
index 000000000..c3d03b78c
--- /dev/null
+++ b/packages/website/ts/components/ui/ethereum_address.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import {EtherScanIcon} from 'ts/components/ui/etherscan_icon';
+import ReactTooltip = require('react-tooltip');
+import {EtherscanLinkSuffixes} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+
+interface EthereumAddressProps {
+ address: string;
+ networkId: number;
+}
+
+export const EthereumAddress = (props: EthereumAddressProps) => {
+ const tooltipId = `${props.address}-ethereum-address`;
+ const truncatedAddress = utils.getAddressBeginAndEnd(props.address);
+ return (
+ <div>
+ <div
+ className="inline"
+ style={{fontSize: 13}}
+ data-tip={true}
+ data-for={tooltipId}
+ >
+ {truncatedAddress}
+ </div>
+ <div className="pl1 inline">
+ <EtherScanIcon
+ addressOrTxHash={props.address}
+ networkId={props.networkId}
+ etherscanLinkSuffixes={EtherscanLinkSuffixes.address}
+ />
+ </div>
+ <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx
new file mode 100644
index 000000000..12044f44b
--- /dev/null
+++ b/packages/website/ts/components/ui/etherscan_icon.tsx
@@ -0,0 +1,50 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import ReactTooltip = require('react-tooltip');
+import {colors} from 'material-ui/styles';
+import {EtherscanLinkSuffixes} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+
+interface EtherScanIconProps {
+ addressOrTxHash: string;
+ etherscanLinkSuffixes: EtherscanLinkSuffixes;
+ networkId: number;
+}
+
+export const EtherScanIcon = (props: EtherScanIconProps) => {
+ const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
+ props.addressOrTxHash, props.networkId, EtherscanLinkSuffixes.address,
+ );
+ const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`;
+ return (
+ <div className="inline">
+ {!_.isUndefined(etherscanLinkIfExists) ?
+ <a
+ href={etherscanLinkIfExists}
+ target="_blank"
+ >
+ {renderIcon()}
+ </a> :
+ <div
+ className="inline"
+ data-tip={true}
+ data-for={transactionTooltipId}
+ >
+ {renderIcon()}
+ <ReactTooltip id={transactionTooltipId}>
+ Your network (id: {props.networkId}) is not supported by Etherscan
+ </ReactTooltip>
+ </div>
+ }
+ </div>
+ );
+};
+
+function renderIcon() {
+ return (
+ <i
+ style={{color: colors.amber600}}
+ className="zmdi zmdi-open-in-new"
+ />
+ );
+}
diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx
new file mode 100644
index 000000000..372785c2f
--- /dev/null
+++ b/packages/website/ts/components/ui/fake_text_field.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {InputLabel} from 'ts/components/ui/input_label';
+import {Styles} from 'ts/types';
+
+const styles: Styles = {
+ hr: {
+ borderBottom: '1px solid rgb(224, 224, 224)',
+ borderLeft: 'none rgb(224, 224, 224)',
+ borderRight: 'none rgb(224, 224, 224)',
+ borderTop: 'none rgb(224, 224, 224)',
+ bottom: 6,
+ boxSizing: 'content-box',
+ margin: 0,
+ position: 'absolute',
+ width: '100%',
+ },
+};
+
+interface FakeTextFieldProps {
+ label?: React.ReactNode | string;
+ children?: any;
+}
+
+export function FakeTextField(props: FakeTextFieldProps) {
+ return (
+ <div className="relative">
+ {props.label !== '' && <InputLabel text={props.label} />}
+ <div className="pb2" style={{height: 23}}>
+ {props.children}
+ </div>
+ <hr style={styles.hr} />
+ </div>
+ );
+}
diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx
new file mode 100644
index 000000000..684aeef68
--- /dev/null
+++ b/packages/website/ts/components/ui/flash_message.tsx
@@ -0,0 +1,40 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import Snackbar from 'material-ui/Snackbar';
+import {Dispatcher} from 'ts/redux/dispatcher';
+
+const SHOW_DURATION_MS = 4000;
+
+interface FlashMessageProps {
+ dispatcher: Dispatcher;
+ flashMessage?: string|React.ReactNode;
+ showDurationMs?: number;
+ bodyStyle?: React.CSSProperties;
+}
+
+interface FlashMessageState {}
+
+export class FlashMessage extends React.Component<FlashMessageProps, FlashMessageState> {
+ public static defaultProps: Partial<FlashMessageProps> = {
+ showDurationMs: SHOW_DURATION_MS,
+ bodyStyle: {},
+ };
+ public render() {
+ if (!_.isUndefined(this.props.flashMessage)) {
+ return (
+ <Snackbar
+ open={true}
+ message={this.props.flashMessage}
+ autoHideDuration={this.props.showDurationMs}
+ onRequestClose={this.onClose.bind(this)}
+ bodyStyle={this.props.bodyStyle}
+ />
+ );
+ } else {
+ return null;
+ }
+ }
+ private onClose() {
+ this.props.dispatcher.hideFlashMessage();
+ }
+}
diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx
new file mode 100644
index 000000000..003b795ef
--- /dev/null
+++ b/packages/website/ts/components/ui/help_tooltip.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import ReactTooltip = require('react-tooltip');
+
+interface HelpTooltipProps {
+ style?: React.CSSProperties;
+ explanation: React.ReactNode;
+}
+
+export const HelpTooltip = (props: HelpTooltipProps) => {
+ return (
+ <div
+ style={{...props.style}}
+ className="inline-block"
+ data-tip={props.explanation}
+ data-for="helpTooltip"
+ data-multiline={true}
+ >
+ <i style={{fontSize: 16}} className="zmdi zmdi-help" />
+ <ReactTooltip id="helpTooltip" />
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx
new file mode 100644
index 000000000..814548fb4
--- /dev/null
+++ b/packages/website/ts/components/ui/identicon.tsx
@@ -0,0 +1,36 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import blockies = require('blockies');
+
+interface IdenticonProps {
+ address: string;
+ diameter: number;
+ style?: React.CSSProperties;
+}
+
+interface IdenticonState {}
+
+export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
+ public static defaultProps: Partial<IdenticonProps> = {
+ style: {},
+ };
+ public render() {
+ let address = this.props.address;
+ if (_.isEmpty(address)) {
+ address = constants.NULL_ADDRESS;
+ }
+ const diameter = this.props.diameter;
+ const icon = blockies({
+ seed: address.toLowerCase(),
+ });
+ return (
+ <div
+ className="circle mx-auto relative transitionFix"
+ style={{width: diameter, height: diameter, overflow: 'hidden', ...this.props.style}}
+ >
+ <img src={icon.toDataURL()} style={{width: diameter, height: diameter, imageRendering: 'pixelated'}}/>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx
new file mode 100644
index 000000000..5866c70b6
--- /dev/null
+++ b/packages/website/ts/components/ui/input_label.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+
+export interface InputLabelProps {
+ text: string | Element | React.ReactNode;
+}
+
+const styles = {
+ label: {
+ color: colors.grey500,
+ fontSize: 12,
+ pointerEvents: 'none',
+ textAlign: 'left',
+ transform: 'scale(0.75) translate(0px, -28px)',
+ transformOrigin: 'left top 0px',
+ transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
+ userSelect: 'none',
+ width: 240,
+ zIndex: 1,
+ },
+};
+
+export const InputLabel = (props: InputLabelProps) => {
+ return (
+ <label style={styles.label}>{props.text}</label>
+ );
+};
diff --git a/packages/website/ts/components/ui/labeled_switcher.tsx b/packages/website/ts/components/ui/labeled_switcher.tsx
new file mode 100644
index 000000000..3ed8ba0a4
--- /dev/null
+++ b/packages/website/ts/components/ui/labeled_switcher.tsx
@@ -0,0 +1,76 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+
+const CUSTOM_BLUE = '#63A6F1';
+
+interface LabeledSwitcherProps {
+ labelLeft: string;
+ labelRight: string;
+ isLeftInitiallySelected: boolean;
+ onLeftLabelClickAsync: () => Promise<boolean>;
+ onRightLabelClickAsync: () => Promise<boolean>;
+}
+
+interface LabeledSwitcherState {
+ isLeftSelected: boolean;
+}
+
+export class LabeledSwitcher extends React.Component<LabeledSwitcherProps, LabeledSwitcherState> {
+ constructor(props: LabeledSwitcherProps) {
+ super(props);
+ this.state = {
+ isLeftSelected: props.isLeftInitiallySelected,
+ };
+ }
+ public render() {
+ const isLeft = true;
+ return (
+ <div
+ className="rounded clearfix"
+ >
+ {this.renderLabel(this.props.labelLeft, isLeft, this.state.isLeftSelected)}
+ {this.renderLabel(this.props.labelRight, !isLeft, !this.state.isLeftSelected)}
+ </div>
+ );
+ }
+ private renderLabel(title: string, isLeft: boolean, isSelected: boolean) {
+ const borderStyle = `2px solid ${isSelected ? '#4F8BCF' : '#DADADA'}`;
+ const style = {
+ cursor: 'pointer',
+ backgroundColor: isSelected ? CUSTOM_BLUE : colors.grey200,
+ color: isSelected ? 'white' : '#A5A5A5',
+ boxShadow: isSelected ? `inset 0px 0px 4px #4083CE` : 'inset 0px 0px 4px #F7F6F6',
+ borderTop: borderStyle,
+ borderBottom: borderStyle,
+ [isLeft ? 'borderLeft' : 'borderRight']: borderStyle,
+ paddingTop: 12,
+ paddingBottom: 12,
+ };
+ return (
+ <div
+ className={`col col-6 center p1 ${isLeft ? 'rounded-left' : 'rounded-right'}`}
+ style={style}
+ onClick={this.onLabelClickAsync.bind(this, isLeft)}
+ >
+ {title}
+ </div>
+ );
+ }
+ private async onLabelClickAsync(isLeft: boolean): Promise<void> {
+ this.setState({
+ isLeftSelected: isLeft,
+ });
+ let didSucceed;
+ if (isLeft) {
+ didSucceed = await this.props.onLeftLabelClickAsync();
+ } else {
+ didSucceed = await this.props.onRightLabelClickAsync();
+ }
+ if (!didSucceed) {
+ this.setState({
+ isLeftSelected: !isLeft,
+ });
+ }
+ }
+}
diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx
new file mode 100644
index 000000000..e93c80ba4
--- /dev/null
+++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx
@@ -0,0 +1,105 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {utils} from 'ts/utils/utils';
+import {Token} from 'ts/types';
+import {Blockchain} from 'ts/blockchain';
+import RaisedButton from 'material-ui/RaisedButton';
+
+const COMPLETE_STATE_SHOW_LENGTH_MS = 2000;
+
+enum ButtonState {
+ READY,
+ LOADING,
+ COMPLETE,
+};
+
+interface LifeCycleRaisedButtonProps {
+ isHidden?: boolean;
+ isDisabled?: boolean;
+ isPrimary?: boolean;
+ labelReady: React.ReactNode|string;
+ labelLoading: React.ReactNode|string;
+ labelComplete: React.ReactNode|string;
+ onClickAsyncFn: () => boolean;
+ backgroundColor?: string;
+ labelColor?: string;
+}
+
+interface LifeCycleRaisedButtonState {
+ buttonState: ButtonState;
+}
+
+export class LifeCycleRaisedButton extends
+ React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> {
+ public static defaultProps: Partial<LifeCycleRaisedButtonProps> = {
+ isDisabled: false,
+ backgroundColor: 'white',
+ labelColor: 'rgb(97, 97, 97)',
+ };
+ private buttonTimeoutId: number;
+ private didUnmount: boolean;
+ constructor(props: LifeCycleRaisedButtonProps) {
+ super(props);
+ this.state = {
+ buttonState: ButtonState.READY,
+ };
+ }
+ public componentWillUnmount() {
+ clearTimeout(this.buttonTimeoutId);
+ this.didUnmount = true;
+ }
+ public render() {
+ if (this.props.isHidden === true) {
+ return <span />;
+ }
+
+ let label;
+ switch (this.state.buttonState) {
+ case ButtonState.READY:
+ label = this.props.labelReady;
+ break;
+ case ButtonState.LOADING:
+ label = this.props.labelLoading;
+ break;
+ case ButtonState.COMPLETE:
+ label = this.props.labelComplete;
+ break;
+ default:
+ throw utils.spawnSwitchErr('ButtonState', this.state.buttonState);
+ }
+ return (
+ <RaisedButton
+ primary={this.props.isPrimary}
+ label={label}
+ style={{width: '100%'}}
+ backgroundColor={this.props.backgroundColor}
+ labelColor={this.props.labelColor}
+ onTouchTap={this.onClickAsync.bind(this)}
+ disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY}
+ />
+ );
+ }
+ public async onClickAsync() {
+ this.setState({
+ buttonState: ButtonState.LOADING,
+ });
+ const didSucceed = await this.props.onClickAsyncFn();
+ if (this.didUnmount) {
+ return; // noop since unmount called before async callback returned.
+ }
+ if (didSucceed) {
+ this.setState({
+ buttonState: ButtonState.COMPLETE,
+ });
+ this.buttonTimeoutId = window.setTimeout(() => {
+ this.setState({
+ buttonState: ButtonState.READY,
+ });
+ }, COMPLETE_STATE_SHOW_LENGTH_MS);
+ } else {
+ this.setState({
+ buttonState: ButtonState.READY,
+ });
+ }
+ }
+}
diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx
new file mode 100644
index 000000000..39c119d8f
--- /dev/null
+++ b/packages/website/ts/components/ui/loading.tsx
@@ -0,0 +1,36 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import Paper from 'material-ui/Paper';
+import {utils} from 'ts/utils/utils';
+import {DefaultPlayer as Video} from 'react-html5video';
+import 'react-html5video/dist/styles.css';
+
+interface LoadingProps {}
+
+interface LoadingState {}
+
+export class Loading extends React.Component<LoadingProps, LoadingState> {
+ public render() {
+ return (
+ <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{height: 500}}>
+ <Paper className="mx-auto" style={{maxWidth: 400}}>
+ {utils.isUserOnMobile() ?
+ <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> :
+ <div style={{pointerEvents: 'none'}}>
+ <Video
+ autoPlay={true}
+ loop={true}
+ muted={true}
+ controls={[]}
+ poster="/images/loading_poster.png"
+ >
+ <source src="/videos/0xAnimation.mp4" type="video/mp4" />
+ </Video>
+ </div>
+ }
+ <div className="center pt2" style={{paddingBottom: 11}}>Connecting to the blockchain...</div>
+ </Paper>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx
new file mode 100644
index 000000000..b9caa91fb
--- /dev/null
+++ b/packages/website/ts/components/ui/menu_item.tsx
@@ -0,0 +1,54 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Link} from 'react-router-dom';
+import {Styles} from 'ts/types';
+import {constants} from 'ts/utils/constants';
+import {colors} from 'material-ui/styles';
+
+interface MenuItemProps {
+ to: string;
+ style?: React.CSSProperties;
+ onClick?: () => void;
+ className?: string;
+}
+
+interface MenuItemState {
+ isHovering: boolean;
+}
+
+export class MenuItem extends React.Component<MenuItemProps, MenuItemState> {
+ public static defaultProps: Partial<MenuItemProps> = {
+ onClick: _.noop,
+ className: '',
+ };
+ public constructor(props: MenuItemProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const menuItemStyles = {
+ cursor: 'pointer',
+ opacity: this.state.isHovering ? 0.5 : 1,
+ };
+ return (
+ <Link to={this.props.to} style={{textDecoration: 'none', ...this.props.style}}>
+ <div
+ onClick={this.props.onClick.bind(this)}
+ className={`mx-auto ${this.props.className}`}
+ style={menuItemStyles}
+ onMouseEnter={this.onToggleHover.bind(this, true)}
+ onMouseLeave={this.onToggleHover.bind(this, false)}
+ >
+ {this.props.children}
+ </div>
+ </Link>
+ );
+ }
+ private onToggleHover(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx
new file mode 100644
index 000000000..b72e75181
--- /dev/null
+++ b/packages/website/ts/components/ui/party.tsx
@@ -0,0 +1,150 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import ReactTooltip = require('react-tooltip');
+import {colors} from 'material-ui/styles';
+import {Identicon} from 'ts/components/ui/identicon';
+import {EtherscanLinkSuffixes} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {EthereumAddress} from 'ts/components/ui/ethereum_address';
+
+const MIN_ADDRESS_WIDTH = 60;
+const IMAGE_DIMENSION = 100;
+const IDENTICON_DIAMETER = 95;
+const CHECK_MARK_GREEN = 'rgb(0, 195, 62)';
+
+interface PartyProps {
+ label: string;
+ address: string;
+ networkId: number;
+ alternativeImage?: string;
+ identiconDiameter?: number;
+ identiconStyle?: React.CSSProperties;
+ isInTokenRegistry?: boolean;
+ hasUniqueNameAndSymbol?: boolean;
+}
+
+interface PartyState {}
+
+export class Party extends React.Component<PartyProps, PartyState> {
+ public static defaultProps: Partial<PartyProps> = {
+ identiconStyle: {},
+ identiconDiameter: IDENTICON_DIAMETER,
+ };
+ public render() {
+ const label = this.props.label;
+ const address = this.props.address;
+ const tooltipId = `${label}-${address}-tooltip`;
+ const identiconDiameter = this.props.identiconDiameter;
+ const addressWidth = identiconDiameter > MIN_ADDRESS_WIDTH ?
+ identiconDiameter : MIN_ADDRESS_WIDTH;
+ const emptyIdenticonStyles = {
+ width: identiconDiameter,
+ height: identiconDiameter,
+ backgroundColor: 'lightgray',
+ marginTop: 13,
+ marginBottom: 10,
+ };
+ const tokenImageStyle = {
+ width: IMAGE_DIMENSION,
+ height: IMAGE_DIMENSION,
+ };
+ const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
+ this.props.address, this.props.networkId, EtherscanLinkSuffixes.address,
+ );
+ const isRegistered = this.props.isInTokenRegistry;
+ const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`;
+ const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`;
+ return (
+ <div style={{overflow: 'hidden'}}>
+ <div className="pb1 center">{label}</div>
+ {_.isEmpty(address) ?
+ <div
+ className="circle mx-auto"
+ style={emptyIdenticonStyles}
+ /> :
+ <a
+ href={etherscanLinkIfExists}
+ target="_blank"
+ >
+ {isRegistered && !_.isUndefined(this.props.alternativeImage) ?
+ <img
+ style={tokenImageStyle}
+ src={this.props.alternativeImage}
+ /> :
+ <div
+ className="mx-auto"
+ style={{height: IMAGE_DIMENSION, width: IMAGE_DIMENSION}}
+ >
+ <Identicon
+ address={this.props.address}
+ diameter={identiconDiameter}
+ style={this.props.identiconStyle}
+ />
+ </div>
+ }
+ </a>
+ }
+ <div
+ className="mx-auto center pt1"
+ >
+ <div style={{height: 25}}>
+ <EthereumAddress address={address} networkId={this.props.networkId} />
+ </div>
+ {!_.isUndefined(this.props.isInTokenRegistry) &&
+ <div>
+ <div
+ data-tip={true}
+ data-for={registeredTooltipId}
+ className="mx-auto"
+ style={{fontSize: 13, width: 127}}
+ >
+ <span style={{color: isRegistered ? CHECK_MARK_GREEN : colors.red500}}>
+ <i
+ className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`}
+ />
+ </span>{' '}
+ <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span>
+ <ReactTooltip id={registeredTooltipId}>
+ {isRegistered ?
+ <div>
+ This token address was found in the token registry<br />
+ smart contract and is therefore believed to be a<br />
+ legitimate token.
+ </div> :
+ <div>
+ This token is not included in the token registry<br />
+ smart contract. We cannot guarantee the legitimacy<br />
+ of this token. Make sure to verify its address on Etherscan.
+ </div>
+ }
+ </ReactTooltip>
+ </div>
+ </div>
+ }
+ {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && !this.props.hasUniqueNameAndSymbol &&
+ <div>
+ <div
+ data-tip={true}
+ data-for={uniqueNameAndSymbolTooltipId}
+ className="mx-auto"
+ style={{fontSize: 13, width: 127}}
+ >
+ <span style={{color: colors.red500}}>
+ <i
+ className="zmdi zmdi-alert-octagon"
+ />
+ </span>{' '}
+ <span>Suspicious token</span>
+ <ReactTooltip id={uniqueNameAndSymbolTooltipId}>
+ This token shares it's name, symbol or both with<br />
+ a token in the 0x Token Registry but it has a different<br />
+ smart contract address. This is most likely a scam token!
+ </ReactTooltip>
+ </div>
+ </div>
+ }
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx
new file mode 100644
index 000000000..f9c73157a
--- /dev/null
+++ b/packages/website/ts/components/ui/required_label.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+
+export interface RequiredLabelProps {
+ label: string|React.ReactNode;
+}
+
+export const RequiredLabel = (props: RequiredLabelProps) => {
+ return (
+ <span>
+ {props.label}
+ <span style={{color: colors.red600}}>*</span>
+ </span>
+ );
+};
diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx
new file mode 100644
index 000000000..12d09ecc4
--- /dev/null
+++ b/packages/website/ts/components/ui/simple_loading.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import CircularProgress from 'material-ui/CircularProgress';
+
+export interface SimpleLoadingProps {
+ message: string;
+}
+
+export const SimpleLoading = (props: SimpleLoadingProps) => {
+ return (
+ <div className="mx-auto pt3" style={{maxWidth: 400, height: 409}}>
+ <div
+ className="relative"
+ style={{top: '50%', transform: 'translateY(-50%)', height: 95}}
+ >
+ <CircularProgress />
+ <div className="pt3 pb3">
+ {props.message}
+ </div>
+ </div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx
new file mode 100644
index 000000000..89bb33d55
--- /dev/null
+++ b/packages/website/ts/components/ui/swap_icon.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import {colors} from 'material-ui/styles';
+
+interface SwapIconProps {
+ swapTokensFn: () => void;
+}
+
+interface SwapIconState {
+ isHovering: boolean;
+}
+
+export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> {
+ public constructor(props: SwapIconProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const swapStyles = {
+ color: this.state.isHovering ? colors.amber600 : colors.amber800,
+ fontSize: 50,
+ };
+ return (
+ <div
+ className="mx-auto pt4"
+ style={{cursor: 'pointer', height: 50, width: 37.5}}
+ onClick={this.props.swapTokensFn}
+ onMouseEnter={this.onToggleHover.bind(this, true)}
+ onMouseLeave={this.onToggleHover.bind(this, false)}
+ >
+ <i
+ style={swapStyles}
+ className="zmdi zmdi-swap"
+ />
+ </div>
+ );
+ }
+ private onToggleHover(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx
new file mode 100644
index 000000000..168c09bd4
--- /dev/null
+++ b/packages/website/ts/components/ui/token_icon.tsx
@@ -0,0 +1,29 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Token} from 'ts/types';
+import {Identicon} from 'ts/components/ui/identicon';
+
+interface TokenIconProps {
+ token: Token;
+ diameter: number;
+}
+
+interface TokenIconState {}
+
+export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> {
+ public render() {
+ const token = this.props.token;
+ const diameter = this.props.diameter;
+ return (
+ <div>
+ {(token.isRegistered && !_.isUndefined(token.iconUrl)) ?
+ <img
+ style={{width: diameter, height: diameter}}
+ src={token.iconUrl}
+ /> :
+ <Identicon address={token.address} diameter={diameter} />
+ }
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx
new file mode 100644
index 000000000..a7d6d1a9e
--- /dev/null
+++ b/packages/website/ts/components/visual_order.tsx
@@ -0,0 +1,77 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {ZeroEx} from '0x.js';
+import {AssetToken, Token, TokenByAddress} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {Party} from 'ts/components/ui/party';
+import {constants} from 'ts/utils/constants';
+
+const PRECISION = 5;
+
+interface VisualOrderProps {
+ orderTakerAddress: string;
+ orderMakerAddress: string;
+ makerAssetToken: AssetToken;
+ takerAssetToken: AssetToken;
+ makerToken: Token;
+ takerToken: Token;
+ networkId: number;
+ tokenByAddress: TokenByAddress;
+ isMakerTokenAddressInRegistry: boolean;
+ isTakerTokenAddressInRegistry: boolean;
+}
+
+interface VisualOrderState {}
+
+export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderState> {
+ public render() {
+ const allTokens = _.values(this.props.tokenByAddress);
+ const makerImage = this.props.makerToken.iconUrl;
+ const takerImage = this.props.takerToken.iconUrl;
+ return (
+ <div>
+ <div className="clearfix">
+ <div className="col col-5 center">
+ <Party
+ label="Send"
+ address={this.props.takerToken.address}
+ alternativeImage={takerImage}
+ networkId={this.props.networkId}
+ isInTokenRegistry={this.props.isTakerTokenAddressInRegistry}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)}
+ />
+ </div>
+ <div className="col col-2 center pt1">
+ <div className="pb1">
+ {this.renderAmount(this.props.takerAssetToken, this.props.takerToken)}
+ </div>
+ <div className="lg-p2 md-p2 sm-p1">
+ <img src="/images/trade_arrows.png" style={{width: 47}} />
+ </div>
+ <div className="pt1">
+ {this.renderAmount(this.props.makerAssetToken, this.props.makerToken)}
+ </div>
+ </div>
+ <div className="col col-5 center">
+ <Party
+ label="Receive"
+ address={this.props.makerToken.address}
+ alternativeImage={makerImage}
+ networkId={this.props.networkId}
+ isInTokenRegistry={this.props.isMakerTokenAddressInRegistry}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderAmount(assetToken: AssetToken, token: Token) {
+ const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals);
+ return (
+ <div style={{fontSize: 13}}>
+ {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol}
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx
new file mode 100644
index 000000000..97b5172e7
--- /dev/null
+++ b/packages/website/ts/containers/generate_order_form.tsx
@@ -0,0 +1,54 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {connect} from 'react-redux';
+import {Store as ReduxStore, Dispatch} from 'redux';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {State} from 'ts/redux/reducer';
+import {Blockchain} from 'ts/blockchain';
+import {GenerateOrderForm as GenerateOrderFormComponent} from 'ts/components/generate_order/generate_order_form';
+import {
+ SideToAssetToken,
+ SignatureData,
+ HashData,
+ TokenByAddress,
+ TokenStateByAddress,
+ BlockchainErrs,
+} from 'ts/types';
+import BigNumber from 'bignumber.js';
+
+interface GenerateOrderFormProps {
+ blockchain: Blockchain;
+ hashData: HashData;
+ dispatcher: Dispatcher;
+}
+
+interface ConnectedState {
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ orderExpiryTimestamp: BigNumber;
+ orderSignatureData: SignatureData;
+ userAddress: string;
+ orderTakerAddress: string;
+ orderSalt: BigNumber;
+ networkId: number;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+}
+
+const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({
+ blockchainErr: state.blockchainErr,
+ blockchainIsLoaded: state.blockchainIsLoaded,
+ orderExpiryTimestamp: state.orderExpiryTimestamp,
+ orderSignatureData: state.orderSignatureData,
+ orderTakerAddress: state.orderTakerAddress,
+ orderSalt: state.orderSalt,
+ networkId: state.networkId,
+ sideToAssetToken: state.sideToAssetToken,
+ tokenByAddress: state.tokenByAddress,
+ tokenStateByAddress: state.tokenStateByAddress,
+ userAddress: state.userAddress,
+});
+
+export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> =
+ connect(mapStateToProps)(GenerateOrderFormComponent);
diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx
new file mode 100644
index 000000000..805058aa3
--- /dev/null
+++ b/packages/website/ts/containers/portal.tsx
@@ -0,0 +1,94 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {connect} from 'react-redux';
+import {Store as ReduxStore, Dispatch} from 'redux';
+import {State} from 'ts/redux/reducer';
+import {constants} from 'ts/utils/constants';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ Side,
+ HashData,
+ TokenByAddress,
+ BlockchainErrs,
+ Fill,
+ Order,
+ ScreenWidths,
+ TokenStateByAddress,
+} from 'ts/types';
+import {
+ Portal as PortalComponent,
+ PortalAllProps as PortalComponentAllProps,
+ PortalPassedProps as PortalComponentPassedProps,
+} from 'ts/components/portal';
+import BigNumber from 'bignumber.js';
+
+interface MapStateToProps {
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ hashData: HashData;
+ networkId: number;
+ nodeVersion: string;
+ orderFillAmount: number;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userEtherBalance: number;
+ screenWidth: ScreenWidths;
+ shouldBlockchainErrDialogBeOpen: boolean;
+ userAddress: string;
+ userSuppliedOrderCache: Order;
+}
+
+interface ConnectedState {}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => {
+ const receiveAssetToken = state.sideToAssetToken[Side.receive];
+ const depositAssetToken = state.sideToAssetToken[Side.deposit];
+ const receiveAddress = !_.isUndefined(receiveAssetToken.address) ?
+ receiveAssetToken.address : constants.NULL_ADDRESS;
+ const depositAddress = !_.isUndefined(depositAssetToken.address) ?
+ depositAssetToken.address : constants.NULL_ADDRESS;
+ const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ?
+ receiveAssetToken.amount : new BigNumber(0);
+ const depositAmount = !_.isUndefined(depositAssetToken.amount) ?
+ depositAssetToken.amount : new BigNumber(0);
+ const hashData = {
+ depositAmount,
+ depositTokenContractAddr: depositAddress,
+ feeRecipientAddress: constants.FEE_RECIPIENT_ADDRESS,
+ makerFee: constants.MAKER_FEE,
+ orderExpiryTimestamp: state.orderExpiryTimestamp,
+ orderMakerAddress: state.userAddress,
+ orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS,
+ receiveAmount,
+ receiveTokenContractAddr: receiveAddress,
+ takerFee: constants.TAKER_FEE,
+ orderSalt: state.orderSalt,
+ };
+ return {
+ blockchainErr: state.blockchainErr,
+ blockchainIsLoaded: state.blockchainIsLoaded,
+ networkId: state.networkId,
+ nodeVersion: state.nodeVersion,
+ orderFillAmount: state.orderFillAmount,
+ hashData,
+ screenWidth: state.screenWidth,
+ shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
+ tokenByAddress: state.tokenByAddress,
+ tokenStateByAddress: state.tokenStateByAddress,
+ userAddress: state.userAddress,
+ userEtherBalance: state.userEtherBalance,
+ userSuppliedOrderCache: state.userSuppliedOrderCache,
+ flashMessage: state.flashMessage,
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const Portal: React.ComponentClass<PortalComponentPassedProps> =
+ connect(mapStateToProps, mapDispatchToProps)(PortalComponent);
diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx
new file mode 100644
index 000000000..5d05bdd2f
--- /dev/null
+++ b/packages/website/ts/containers/smart_contracts_documentation.tsx
@@ -0,0 +1,31 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {connect} from 'react-redux';
+import {Store as ReduxStore, Dispatch} from 'redux';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {State} from 'ts/redux/reducer';
+import {
+ SmartContractsDocumentation as SmartContractsDocumentationComponent,
+ SmartContractsDocumentationAllProps,
+} from 'ts/pages/documentation/smart_contracts_documentation';
+
+interface ConnectedState {
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, ownProps: SmartContractsDocumentationAllProps): ConnectedState => ({
+ docsVersion: state.docsVersion,
+ availableDocVersions: state.availableDocVersions,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const SmartContractsDocumentation: React.ComponentClass<SmartContractsDocumentationAllProps> =
+ connect(mapStateToProps, mapDispatchToProps)(SmartContractsDocumentationComponent);
diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx
new file mode 100644
index 000000000..a5b8298b5
--- /dev/null
+++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx
@@ -0,0 +1,33 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {connect} from 'react-redux';
+import {Store as ReduxStore, Dispatch} from 'redux';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {State} from 'ts/redux/reducer';
+import {Blockchain} from 'ts/blockchain';
+import {
+ ZeroExJSDocumentation as ZeroExJSDocumentationComponent,
+ ZeroExJSDocumentationAllProps,
+} from 'ts/pages/documentation/zero_ex_js_documentation';
+import BigNumber from 'bignumber.js';
+
+interface ConnectedState {
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, ownProps: ZeroExJSDocumentationAllProps): ConnectedState => ({
+ docsVersion: state.docsVersion,
+ availableDocVersions: state.availableDocVersions,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const ZeroExJSDocumentation: React.ComponentClass<ZeroExJSDocumentationAllProps> =
+ connect(mapStateToProps, mapDispatchToProps)(ZeroExJSDocumentationComponent);
diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts
new file mode 100644
index 000000000..ee449ecfd
--- /dev/null
+++ b/packages/website/ts/globals.d.ts
@@ -0,0 +1,154 @@
+declare module 'react-tooltip';
+declare module 'react-router-hash-link';
+declare module 'es6-promisify';
+declare module 'truffle-contract';
+declare module 'ethereumjs-util';
+declare module 'keccak';
+declare module 'web3-provider-engine';
+declare module 'whatwg-fetch';
+declare module 'react-html5video';
+declare module 'web3-provider-engine/subproviders/filters';
+declare module 'thenby';
+declare module 'react-highlight';
+declare module 'react-recaptcha';
+declare module 'react-document-title';
+declare module 'ledgerco';
+declare module 'ethereumjs-tx';
+
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
+
+// find-version declarations
+declare function findVersions(version: string): string[];
+declare module 'find-versions' {
+ export = findVersions;
+}
+
+// compare-version declarations
+declare function compareVersions(firstVersion: string, secondVersion: string): number;
+declare module 'compare-versions' {
+ export = compareVersions;
+}
+
+// semver-sort declarations
+declare module 'semver-sort' {
+ const desc: (versions: string[]) => string[];
+}
+
+// xml-js declarations
+declare interface XML2JSONOpts {
+ compact?: boolean;
+ spaces?: number;
+}
+declare module 'xml-js' {
+ const xml2json: (xml: string, opts: XML2JSONOpts) => string;
+}
+
+// This will be defined by default in TS 2.4
+// Source: https://github.com/Microsoft/TypeScript/issues/12364
+interface System {
+ import<T>(module: string): Promise<T>;
+}
+declare var System: System;
+
+// ethereum-address declarations
+declare module 'ethereum-address' {
+ export const isAddress: (address: string) => boolean;
+}
+
+// jsonschema declarations
+// Source: https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts
+declare interface Schema {
+ id?: string;
+ $schema?: string;
+ title?: string;
+ description?: string;
+ multipleOf?: number;
+ maximum?: number;
+ exclusiveMaximum?: boolean;
+ minimum?: number;
+ exclusiveMinimum?: boolean;
+ maxLength?: number;
+ minLength?: number;
+ pattern?: string;
+ additionalItems?: boolean | Schema;
+ items?: Schema | Schema[];
+ maxItems?: number;
+ minItems?: number;
+ uniqueItems?: boolean;
+ maxProperties?: number;
+ minProperties?: number;
+ required?: string[];
+ additionalProperties?: boolean | Schema;
+ definitions?: {
+ [name: string]: Schema;
+ };
+ properties?: {
+ [name: string]: Schema;
+ };
+ patternProperties?: {
+ [name: string]: Schema;
+ };
+ dependencies?: {
+ [name: string]: Schema | string[];
+ };
+ 'enum'?: any[];
+ type?: string | string[];
+ allOf?: Schema[];
+ anyOf?: Schema[];
+ oneOf?: Schema[];
+ not?: Schema;
+ // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts
+ // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194
+ // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files
+ // As soon as it gets merged we should be good to use types from 'jsonschema' package
+ $ref?: string;
+}
+
+// blockies declarations
+declare interface BlockiesIcon {
+ toDataURL(): string;
+}
+declare interface BlockiesConfig {
+ seed: string;
+}
+declare function blockies(config: BlockiesConfig): BlockiesIcon;
+declare module 'blockies' {
+ export = blockies;
+}
+
+// is-mobile declarations
+declare function isMobile(): boolean;
+declare module 'is-mobile' {
+ export = isMobile;
+}
+
+// web3-provider-engine declarations
+declare class Subprovider {}
+declare module 'web3-provider-engine/subproviders/subprovider' {
+ export = Subprovider;
+}
+declare class RpcSubprovider {
+ constructor(options: {rpcUrl: string});
+ public handleRequest(payload: any, next: any, end: (err?: Error, data?: any) => void): void;
+}
+declare module 'web3-provider-engine/subproviders/rpc' {
+ export = RpcSubprovider;
+}
+declare class HookedWalletSubprovider {
+ constructor(wallet: any);
+}
+declare module 'web3-provider-engine/subproviders/hooked-wallet' {
+ export = HookedWalletSubprovider;
+}
+
+declare interface Artifact {
+ abi: any;
+ networks: {[networkId: number]: {
+ address: string;
+ }};
+}
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
new file mode 100644
index 000000000..ed4d09956
--- /dev/null
+++ b/packages/website/ts/index.tsx
@@ -0,0 +1,116 @@
+// Polyfills
+import 'whatwg-fetch';
+
+import * as React from 'react';
+import {render} from 'react-dom';
+import {Provider} from 'react-redux';
+import {createStore, Store as ReduxStore} from 'redux';
+import BigNumber from 'bignumber.js';
+import {constants} from 'ts/utils/constants';
+import {Landing} from 'ts/pages/landing/landing';
+import {FAQ} from 'ts/pages/faq/faq';
+import {About} from 'ts/pages/about/about';
+import {Wiki} from 'ts/pages/wiki/wiki';
+import {NotFound} from 'ts/pages/not_found';
+import {createLazyComponent} from 'ts/lazy_component';
+import {State, reducer} from 'ts/redux/reducer';
+import {colors, getMuiTheme, MuiThemeProvider} from 'material-ui/styles';
+import {Switch, BrowserRouter as Router, Route, Link, Redirect} from 'react-router-dom';
+import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage';
+import * as injectTapEventPlugin from 'react-tap-event-plugin';
+import {WebsitePaths} from 'ts/types';
+injectTapEventPlugin();
+
+// By default BigNumber's `toString` method converts to exponential notation if the value has
+// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number
+BigNumber.config({
+ EXPONENTIAL_AT: 1000,
+});
+
+// Check if we've introduced an update that requires us to clear the tradeHistory local storage entries
+tradeHistoryStorage.clearIfRequired();
+
+const CUSTOM_GREY = 'rgb(39, 39, 39)';
+const CUSTOM_GREEN = 'rgb(102, 222, 117)';
+const CUSTOM_DARKER_GREEN = 'rgb(77, 197, 92)';
+
+import 'basscss/css/basscss.css';
+import 'less/all.less';
+
+const muiTheme = getMuiTheme({
+ appBar: {
+ height: 45,
+ color: 'white',
+ textColor: 'black',
+ },
+ palette: {
+ pickerHeaderColor: constants.CUSTOM_BLUE,
+ primary1Color: constants.CUSTOM_BLUE,
+ primary2Color: constants.CUSTOM_BLUE,
+ textColor: colors.grey700,
+ },
+ datePicker: {
+ color: colors.grey700,
+ textColor: 'white',
+ calendarTextColor: 'white',
+ selectColor: CUSTOM_GREY,
+ selectTextColor: 'white',
+ },
+ timePicker: {
+ color: colors.grey700,
+ textColor: 'white',
+ accentColor: 'white',
+ headerColor: CUSTOM_GREY,
+ selectColor: CUSTOM_GREY,
+ selectTextColor: CUSTOM_GREY,
+ },
+ toggle: {
+ thumbOnColor: CUSTOM_GREEN,
+ trackOnColor: CUSTOM_DARKER_GREEN,
+ },
+});
+
+// We pass modulePromise returning lambda instead of module promise,
+// cause we only want to import the module when the user navigates to the page.
+// At the same time webpack statically parses for System.import() to determine bundle chunk split points
+// so each lazy import needs it's own `System.import()` declaration.
+const LazyPortal = createLazyComponent(
+ 'Portal', () => System.import<any>(/* webpackChunkName: "portal" */'ts/containers/portal'),
+);
+const LazyZeroExJSDocumentation = createLazyComponent(
+ 'ZeroExJSDocumentation',
+ () => System.import<any>(/* webpackChunkName: "zeroExDocs" */'ts/containers/zero_ex_js_documentation'),
+);
+const LazySmartContractsDocumentation = createLazyComponent(
+ 'SmartContractsDocumentation',
+ () => System.import<any>(/* webpackChunkName: "smartContractDocs" */'ts/containers/smart_contracts_documentation'),
+);
+
+const store: ReduxStore<State> = createStore(reducer);
+render(
+ <Router>
+ <div>
+ <MuiThemeProvider muiTheme={muiTheme}>
+ <Provider store={store}>
+ <div>
+ <Switch>
+ <Route exact={true} path="/" component={Landing as any} />
+ <Redirect from="/otc" to={`${WebsitePaths.Portal}`}/>
+ <Route path={`${WebsitePaths.Portal}`} component={LazyPortal} />
+ <Route path={`${WebsitePaths.FAQ}`} component={FAQ as any} />
+ <Route path={`${WebsitePaths.About}`} component={About as any} />
+ <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} />
+ <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} />
+ <Route
+ path={`${WebsitePaths.SmartContracts}/:version?`}
+ component={LazySmartContractsDocumentation}
+ />
+ <Route component={NotFound as any} />
+ </Switch>
+ </div>
+ </Provider>
+ </MuiThemeProvider>
+ </div>
+ </Router>,
+ document.getElementById('app'),
+);
diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx
new file mode 100644
index 000000000..7052b7be6
--- /dev/null
+++ b/packages/website/ts/lazy_component.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react';
+import * as _ from 'lodash';
+
+interface LazyComponentProps {
+ reactComponentPromise: Promise<React.ComponentClass<any>>;
+ reactComponentProps: any;
+}
+
+interface LazyComponentState {
+ component?: React.ComponentClass<any>;
+}
+
+/**
+ * This component is used for rendering components that are lazily loaded from other chunks.
+ * Source: https://reacttraining.com/react-router/web/guides/code-splitting
+ */
+export class LazyComponent extends React.Component<LazyComponentProps, LazyComponentState> {
+ constructor(props: LazyComponentProps) {
+ super(props);
+ this.state = {
+ component: undefined,
+ };
+ }
+ public componentWillMount() {
+ this.loadComponentFireAndForgetAsync(this.props);
+ }
+ public componentWillReceiveProps(nextProps: LazyComponentProps) {
+ if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) {
+ this.loadComponentFireAndForgetAsync(nextProps);
+ }
+ }
+ public render() {
+ return _.isUndefined(this.state.component) ?
+ null :
+ React.createElement(this.state.component, this.props.reactComponentProps);
+ }
+ private async loadComponentFireAndForgetAsync(props: LazyComponentProps) {
+ const component = await props.reactComponentPromise;
+ this.setState({
+ component,
+ });
+ }
+}
+
+/**
+ * [createLazyComponent description]
+ * @param componentName name of exported component
+ * @param lazyImport lambda returning module promise
+ * we pass a lambda because we only want to require a module if it's used
+ * @example `const LazyPortal = createLazyComponent('Portal', () => System.import<any>('ts/containers/portal'));``
+ */
+export const createLazyComponent = (componentName: string, lazyImport: () => Promise<any>) => {
+ return (props: any) => {
+ const reactComponentPromise = (async (): Promise<React.ComponentClass<any>> => {
+ const mod = await lazyImport();
+ const component = mod[componentName];
+ return component;
+ })();
+ return (
+ <LazyComponent
+ reactComponentPromise={reactComponentPromise}
+ reactComponentProps={props}
+ />
+ );
+ };
+};
diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts
new file mode 100644
index 000000000..d94e6877b
--- /dev/null
+++ b/packages/website/ts/local_storage/local_storage.ts
@@ -0,0 +1,35 @@
+import * as _ from 'lodash';
+
+export const localStorage = {
+ doesExist() {
+ return !!window.localStorage;
+ },
+ getItemIfExists(key: string): string {
+ if (!this.doesExist) {
+ return undefined;
+ }
+ const item = window.localStorage.getItem(key);
+ if (_.isNull(item) || item === 'undefined') {
+ return '';
+ }
+ return item;
+ },
+ setItem(key: string, value: string) {
+ if (!this.doesExist || _.isUndefined(value)) {
+ return;
+ }
+ window.localStorage.setItem(key, value);
+ },
+ removeItem(key: string) {
+ if (!this.doesExist) {
+ return;
+ }
+ window.localStorage.removeItem(key);
+ },
+ getAllKeys(): string[] {
+ if (!this.doesExist) {
+ return [];
+ }
+ return _.keys(window.localStorage);
+ },
+};
diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts
new file mode 100644
index 000000000..0b54a66e0
--- /dev/null
+++ b/packages/website/ts/local_storage/tracked_token_storage.ts
@@ -0,0 +1,56 @@
+import * as _ from 'lodash';
+import {Token, TrackedTokensByNetworkId} from 'ts/types';
+import {localStorage} from 'ts/local_storage/local_storage';
+
+const TRACKED_TOKENS_KEY = 'trackedTokens';
+
+export const trackedTokenStorage = {
+ addTrackedTokenToUser(userAddress: string, networkId: number, token: Token) {
+ const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress();
+ let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress];
+ if (_.isUndefined(trackedTokensByNetworkId)) {
+ trackedTokensByNetworkId = {};
+ }
+ const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) ?
+ trackedTokensByNetworkId[networkId] :
+ [];
+ trackedTokens.push(token);
+ trackedTokensByNetworkId[networkId] = trackedTokens;
+ trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId;
+ const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress);
+ localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString);
+ },
+ getTrackedTokensByUserAddress(): TrackedTokensByNetworkId {
+ const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY);
+ if (_.isEmpty(trackedTokensJSONString)) {
+ return {};
+ }
+ const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
+ return trackedTokensByUserAddress;
+ },
+ getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] {
+ const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY);
+ if (_.isEmpty(trackedTokensJSONString)) {
+ return undefined;
+ }
+ const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
+ const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress];
+ if (_.isUndefined(trackedTokensByNetworkId)) {
+ return undefined;
+ }
+ const trackedTokens = trackedTokensByNetworkId[networkId];
+ return trackedTokens;
+ },
+ removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string) {
+ const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress();
+ const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress];
+ const trackedTokens = trackedTokensByNetworkId[networkId];
+ const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => {
+ return token.address !== tokenAddress;
+ });
+ trackedTokensByNetworkId[networkId] = remainingTrackedTokens;
+ trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId;
+ const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress);
+ localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString);
+ },
+};
diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx
new file mode 100644
index 000000000..415b380b7
--- /dev/null
+++ b/packages/website/ts/local_storage/trade_history_storage.tsx
@@ -0,0 +1,82 @@
+import * as _ from 'lodash';
+import {Fill} from 'ts/types';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {localStorage} from 'ts/local_storage/local_storage';
+import ethUtil = require('ethereumjs-util');
+import BigNumber from 'bignumber.js';
+
+const FILLS_KEY = 'fills';
+const FILLS_LATEST_BLOCK = 'fillsLatestBlock';
+const FILL_CLEAR_KEY = 'lastClearFillDate';
+
+export const tradeHistoryStorage = {
+ // Clear all fill related localStorage if we've updated the config variable in an update
+ // that introduced a backward incompatible change requiring the user to re-fetch the fills from
+ // the blockchain
+ clearIfRequired() {
+ const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY);
+ if (lastClearFillDate !== configs.lastLocalStorageFillClearanceDate) {
+ const localStorageKeys = localStorage.getAllKeys();
+ _.each(localStorageKeys, key => {
+ if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) {
+ localStorage.removeItem(key);
+ }
+ });
+ }
+ localStorage.setItem(FILL_CLEAR_KEY, configs.lastLocalStorageFillClearanceDate);
+ },
+ addFillToUser(userAddress: string, networkId: number, fill: Fill) {
+ const fillsByHash = this.getUserFillsByHash(userAddress, networkId);
+ const fillHash = this._getFillHash(fill);
+ const doesFillExist = !_.isUndefined(fillsByHash[fillHash]);
+ if (doesFillExist) {
+ return;
+ }
+ fillsByHash[fillHash] = fill;
+ const userFillsJSONString = JSON.stringify(fillsByHash);
+ const userFillsKey = this._getUserFillsKey(userAddress, networkId);
+ localStorage.setItem(userFillsKey, userFillsJSONString);
+ },
+ getUserFillsByHash(userAddress: string, networkId: number): {[fillHash: string]: Fill} {
+ const userFillsKey = this._getUserFillsKey(userAddress, networkId);
+ const userFillsJSONString = localStorage.getItemIfExists(userFillsKey);
+ if (_.isEmpty(userFillsJSONString)) {
+ return {};
+ }
+ const userFillsByHash = JSON.parse(userFillsJSONString);
+ _.each(userFillsByHash, (fill, hash) => {
+ fill.paidMakerFee = new BigNumber(fill.paidMakerFee);
+ fill.paidTakerFee = new BigNumber(fill.paidTakerFee);
+ fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount);
+ fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount);
+ });
+ return userFillsByHash;
+ },
+ getFillsLatestBlock(userAddress: string, networkId: number): number {
+ const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId);
+ const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey);
+ if (_.isEmpty(blockNumberStr)) {
+ return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId];
+ }
+ const blockNumber = _.parseInt(blockNumberStr);
+ return blockNumber;
+ },
+ setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) {
+ const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId);
+ localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`);
+ },
+ _getUserFillsKey(userAddress: string, networkId: number) {
+ const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`;
+ return userFillsKey;
+ },
+ _getFillsLatestBlockKey(userAddress: string, networkId: number) {
+ const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`;
+ return userFillsLatestBlockKey;
+ },
+ _getFillHash(fill: Fill): string {
+ const fillJSON = JSON.stringify(fill);
+ const fillHash = ethUtil.sha256(fillJSON);
+ return fillHash.toString('hex');
+ },
+};
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
new file mode 100644
index 000000000..8859fb00a
--- /dev/null
+++ b/packages/website/ts/pages/about/about.tsx
@@ -0,0 +1,253 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {Styles, ProfileInfo} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+import {Question} from 'ts/pages/faq/question';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {Profile} from 'ts/pages/about/profile';
+
+const CUSTOM_BACKGROUND_COLOR = '#F0F0F0';
+const CUSTOM_GRAY = '#4C4C4C';
+const CUSTOM_LIGHT_GRAY = '#A2A2A2';
+
+const teamRow1: ProfileInfo[] = [
+ {
+ name: 'Will Warren',
+ title: 'Co-founder & CEO',
+ description: `Smart contract R&D. Previously applied physics at Los Alamos \
+ Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`,
+ image: '/images/team/will.jpg',
+ linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/',
+ github: 'https://github.com/willwarren89',
+ medium: 'https://medium.com/@willwarren89',
+ },
+ {
+ name: 'Amir Bandeali',
+ title: 'Co-founder & CTO',
+ description: `Smart contract R&D. Previously fixed income trader at DRW. \
+ Finance at University of Illinois, Urbana-Champaign.`,
+ image: '/images/team/amir.jpeg',
+ linkedIn: 'https://www.linkedin.com/in/abandeali1/',
+ github: 'https://github.com/abandeali1',
+ medium: 'https://medium.com/@abandeali1',
+ },
+ {
+ name: 'Fabio Berger',
+ title: 'Senior Engineer',
+ description: `Full-stack blockchain engineer. Previously software engineer \
+ at Airtable and founder of WealthLift. Computer science at Duke.`,
+ image: '/images/team/fabio.jpg',
+ linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/',
+ github: 'https://github.com/fabioberger',
+ medium: 'https://medium.com/@fabioberger',
+ },
+ {
+ name: 'Alex Xu',
+ title: 'Director of Operations',
+ description: `Strategy and operations. Previously digital marketing at Google \
+ and vendor management at Amazon. Economics at UC San Diego.`,
+ image: '/images/team/alex.jpg',
+ linkedIn: 'https://www.linkedin.com/in/alex-xu/',
+ github: '',
+ medium: '',
+ },
+];
+
+const teamRow2: ProfileInfo[] = [
+ {
+ name: 'Leonid Logvinov',
+ title: 'Engineer',
+ description: `Full-stack blockchain engineer. Previously blockchain engineer \
+ at Neufund. Computer science at University of Warsaw.`,
+ image: '/images/team/leonid.png',
+ linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/',
+ github: 'https://github.com/LogvinovLeon',
+ medium: '',
+ },
+ {
+ name: 'Ben Burns',
+ title: 'Designer',
+ description: `Product, motion, and graphic designer. Previously designer \
+ at Airtable and Apple. Digital Design at University of Cincinnati.`,
+ image: '/images/team/ben.jpg',
+ linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/',
+ github: '',
+ medium: '',
+ },
+ {
+ name: 'Philippe Castonguay',
+ title: 'Dev Relations Manager',
+ description: `Developer relations. Previously computational neuroscience \
+ research at Janelia. Statistics at Western University. MA Dropout.`,
+ image: '/images/team/philippe.png',
+ linkedIn: '',
+ github: 'https://github.com/PhABC',
+ medium: '',
+ },
+ {
+ name: 'Brandon Millman',
+ title: 'Senior Engineer',
+ description: `Full-stack engineer. Previously senior software engineer at \
+ Twitter. Electrical and Computer Engineering at Duke.`,
+ image: '/images/team/brandon.png',
+ linkedIn: 'https://www.linkedin.com/company-beta/17942619/',
+ },
+];
+
+const advisors: ProfileInfo[] = [
+ {
+ name: 'Fred Ehrsam',
+ description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.',
+ image: '/images/advisors/fred.jpg',
+ linkedIn: 'https://www.linkedin.com/in/fredehrsam/',
+ medium: 'https://medium.com/@FEhrsam',
+ twitter: 'https://twitter.com/FEhrsam',
+ },
+ {
+ name: 'Olaf Carlson-Wee',
+ image: '/images/advisors/olaf.png',
+ description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.',
+ linkedIn: 'https://www.linkedin.com/in/olafcw/',
+ angellist: 'https://angel.co/olafcw',
+ },
+ {
+ name: 'Joey Krug',
+ description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`,
+ image: '/images/advisors/joey.jpg',
+ linkedIn: 'https://www.linkedin.com/in/joeykrug/',
+ github: 'https://github.com/joeykrug',
+ angellist: 'https://angel.co/joeykrug',
+ },
+ {
+ name: 'Linda Xie',
+ description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.',
+ image: '/images/advisors/linda.jpg',
+ linkedIn: 'https://www.linkedin.com/in/lindaxie/',
+ medium: 'https://medium.com/@linda.xie',
+ twitter: 'https://twitter.com/ljxie',
+ },
+];
+
+export interface AboutProps {
+ source: string;
+ location: Location;
+}
+
+interface AboutState {}
+
+const styles: Styles = {
+ header: {
+ fontFamily: 'Roboto Mono',
+ fontSize: 36,
+ color: 'black',
+ paddingTop: 110,
+ },
+};
+
+export class About extends React.Component<AboutProps, AboutState> {
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div style={{backgroundColor: CUSTOM_BACKGROUND_COLOR}}>
+ <DocumentTitle title="0x About Us"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ style={{backgroundColor: CUSTOM_BACKGROUND_COLOR}}
+ />
+ <div
+ id="about"
+ className="mx-auto max-width-4 py4"
+ style={{color: colors.grey800}}
+ >
+ <div
+ className="mx-auto pb4 sm-px3"
+ style={{maxWidth: 435}}
+ >
+ <div
+ style={styles.header}
+ >
+ About us:
+ </div>
+ <div
+ className="pt3"
+ style={{fontSize: 17, color: CUSTOM_GRAY, lineHeight: 1.5}}
+ >
+ Our team is a diverse and globally distributed group with backgrounds
+ in engineering, research, business and design. We are passionate about
+ decentralized technology and its potential to act as an equalizing force
+ in the world.
+ </div>
+ </div>
+ <div className="pt3 md-px4 lg-px0">
+ <div className="clearfix pb3">
+ {this.renderProfiles(teamRow1)}
+ </div>
+ <div className="clearfix">
+ {this.renderProfiles(teamRow2)}
+ </div>
+ </div>
+ <div className="pt3 pb2">
+ <div
+ className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3"
+ style={{color: CUSTOM_LIGHT_GRAY, fontSize: 24, fontFamily: 'Roboto Mono'}}
+ >
+ Advisors:
+ </div>
+ <div className="clearfix">
+ {this.renderProfiles(advisors)}
+ </div>
+ </div>
+ <div className="mx-auto py4 sm-px3" style={{maxWidth: 308}}>
+ <div
+ className="pb2"
+ style={{fontSize: 30, color: CUSTOM_GRAY, fontFamily: 'Roboto Mono', letterSpacing: 7.5}}
+ >
+ WE'RE HIRING
+ </div>
+ <div
+ className="pb4 mb4"
+ style={{fontSize: 16, color: CUSTOM_GRAY, lineHeight: 1.5, letterSpacing: '0.5px'}}
+ >
+ We are seeking outstanding candidates to{' '}
+ <a
+ href={constants.ANGELLIST_URL}
+ target="_blank"
+ style={{color: 'black'}}
+ >
+ join our team
+ </a>
+ . We value passion, diversity and unique perspectives.
+ </div>
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderProfiles(profiles: ProfileInfo[]) {
+ const numIndiv = profiles.length;
+ const colSize = utils.getColSize(profiles.length);
+ return _.map(profiles, profile => {
+ return (
+ <div
+ key={`profile-${profile.name}`}
+ >
+ <Profile
+ colSize={colSize}
+ profileInfo={profile}
+ />
+ </div>
+ );
+ });
+ }
+}
diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx
new file mode 100644
index 000000000..6c48a8553
--- /dev/null
+++ b/packages/website/ts/pages/about/profile.tsx
@@ -0,0 +1,99 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {utils} from 'ts/utils/utils';
+import {Element as ScrollElement} from 'react-scroll';
+import {Styles, ProfileInfo} from 'ts/types';
+
+const IMAGE_DIMENSION = 149;
+const styles: Styles = {
+ subheader: {
+ textTransform: 'uppercase',
+ fontSize: 32,
+ margin: 0,
+ },
+ imageContainer: {
+ width: IMAGE_DIMENSION,
+ height: IMAGE_DIMENSION,
+ boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px',
+ },
+};
+
+interface ProfileProps {
+ colSize: number;
+ profileInfo: ProfileInfo;
+}
+
+export function Profile(props: ProfileProps) {
+ return (
+ <div
+ className={`lg-col md-col lg-col-${props.colSize} md-col-6`}
+ >
+ <div
+ style={{maxWidth: 300}}
+ className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"
+ >
+ <div
+ className="circle overflow-hidden mx-auto"
+ style={styles.imageContainer}
+ >
+ <img
+ width={IMAGE_DIMENSION}
+ src={props.profileInfo.image}
+ />
+ </div>
+ <div
+ className="center"
+ style={{fontSize: 18, fontWeight: 'bold', paddingTop: 20}}
+ >
+ {props.profileInfo.name}
+ </div>
+ {!_.isUndefined(props.profileInfo.title) &&
+ <div
+ className="pt1 center"
+ style={{fontSize: 14, fontFamily: 'Roboto Mono', color: '#818181'}}
+ >
+ {props.profileInfo.title.toUpperCase()}
+ </div>
+ }
+ <div
+ style={{minHeight: 60, lineHeight: 1.4}}
+ className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"
+ >
+ {props.profileInfo.description}
+ </div>
+ <div className="flex pb3 mx-auto sm-hide xs-hide" style={{width: 180, opacity: 0.5}}>
+ {renderSocialMediaIcons(props.profileInfo)}
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function renderSocialMediaIcons(profileInfo: ProfileInfo) {
+ const icons = [
+ renderSocialMediaIcon('zmdi-github-box', profileInfo.github),
+ renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn),
+ renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter),
+ ];
+ return icons;
+}
+
+function renderSocialMediaIcon(iconName: string, url: string) {
+ if (_.isEmpty(url)) {
+ return null;
+ }
+
+ return (
+ <div key={url} className="pr1">
+ <a
+ href={url}
+ style={{color: 'inherit'}}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <i className={`zmdi ${iconName}`} style={{...styles.socalIcon}} />
+ </a>
+ </div>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx
new file mode 100644
index 000000000..78bbdc069
--- /dev/null
+++ b/packages/website/ts/pages/documentation/comment.tsx
@@ -0,0 +1,24 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block';
+
+interface CommentProps {
+ comment: string;
+ className?: string;
+}
+
+const defaultProps = {
+ className: '',
+};
+
+export const Comment: React.SFC<CommentProps> = (props: CommentProps) => {
+ return (
+ <div className={`${props.className} comment`}>
+ <ReactMarkdown
+ source={props.comment}
+ renderers={{CodeBlock: MarkdownCodeBlock}}
+ />
+ </div>
+ );
+};
diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx
new file mode 100644
index 000000000..aca8af832
--- /dev/null
+++ b/packages/website/ts/pages/documentation/custom_enum.tsx
@@ -0,0 +1,31 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {utils} from 'ts/utils/utils';
+import {CustomType} from 'ts/types';
+
+const STRING_ENUM_CODE_PREFIX = ' strEnum(';
+
+interface CustomEnumProps {
+ type: CustomType;
+}
+
+// This component renders custom string enums that was a work-around for versions of
+// TypeScript <2.4.0 that did not support them natively. We keep it around to support
+// older versions of 0x.js <0.9.0
+export function CustomEnum(props: CustomEnumProps) {
+ const type = props.type;
+ if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) {
+ utils.consoleLog('We do not yet support `Variable` types that are not strEnums');
+ return null;
+ }
+ // Remove the prefix and postfix, leaving only the strEnum values without quotes.
+ const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, '');
+ return (
+ <span>
+ {`{`}
+ {'\t'}{enumValues}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx
new file mode 100644
index 000000000..9364a5d31
--- /dev/null
+++ b/packages/website/ts/pages/documentation/enum.tsx
@@ -0,0 +1,26 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {utils} from 'ts/utils/utils';
+import {TypeDocNode, EnumValue} from 'ts/types';
+
+const STRING_ENUM_CODE_PREFIX = ' strEnum(';
+
+interface EnumProps {
+ values: EnumValue[];
+}
+
+export function Enum(props: EnumProps) {
+ const values = _.map(props.values, (value, i) => {
+ const isLast = i === props.values.length - 1;
+ const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : '';
+ return `\n\t${value.name}${defaultValueIfAny},`;
+ });
+ return (
+ <span>
+ {`{`}
+ {values}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx
new file mode 100644
index 000000000..58271e98f
--- /dev/null
+++ b/packages/website/ts/pages/documentation/event_definition.tsx
@@ -0,0 +1,80 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {Event, EventArg, HeaderSizes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+
+const KEYWORD_COLOR = '#a81ca6';
+const CUSTOM_GREEN = 'rgb(77, 162, 75)';
+
+interface EventDefinitionProps {
+ event: Event;
+}
+
+interface EventDefinitionState {
+ shouldShowAnchor: boolean;
+}
+
+export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> {
+ constructor(props: EventDefinitionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const event = this.props.event;
+ return (
+ <div
+ id={event.name}
+ className="pb2"
+ style={{overflow: 'hidden', width: '100%'}}
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={`Event ${event.name}`}
+ id={event.name}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ <div style={{fontSize: 16}}>
+ <pre>
+ <code className="hljs">
+ {this.renderEventCode()}
+ </code>
+ </pre>
+ </div>
+ </div>
+ );
+ }
+ private renderEventCode() {
+ const indexed = <span style={{color: CUSTOM_GREEN}}> indexed</span>;
+ const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => {
+ return (
+ <span key={`eventArg-${eventArg.name}`}>
+ {eventArg.name}{eventArg.isIndexed ? indexed : ''}: <Type type={eventArg.type} />,
+ </span>
+ );
+ });
+ const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '\n\t', curr];
+ });
+ return (
+ <span>
+ {`{`}
+ <br />
+ {'\t'}{argList}
+ <br />
+ {`}`}
+ </span>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx
new file mode 100644
index 000000000..9e40b8901
--- /dev/null
+++ b/packages/website/ts/pages/documentation/interface.tsx
@@ -0,0 +1,54 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {CustomType, TypeDocTypes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+
+interface InterfaceProps {
+ type: CustomType;
+}
+
+export function Interface(props: InterfaceProps) {
+ const type = props.type;
+ const properties = _.map(type.children, property => {
+ return (
+ <span key={`property-${property.name}-${property.type}-${type.name}`}>
+ {property.name}:{' '}
+ {property.type.typeDocType !== TypeDocTypes.Reflection ?
+ <Type type={property.type} /> :
+ <MethodSignature
+ method={property.type.method}
+ shouldHideMethodName={true}
+ shouldUseArrowSyntax={true}
+ />
+ },
+ </span>
+ );
+ });
+ const hasIndexSignature = !_.isUndefined(type.indexSignature);
+ if (hasIndexSignature) {
+ const is = type.indexSignature;
+ const param = (
+ <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}>
+ {is.keyName}: <Type type={is.keyType} />
+ </span>
+ );
+ properties.push((
+ <span key={`indexSignature-${type.name}-${is.keyType.name}`}>
+ [{param}]: {is.valueName},
+ </span>
+ ));
+ }
+ const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '\n\t', curr];
+ });
+ return (
+ <span>
+ {`{`}
+ <br />
+ {'\t'}{propertyList}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx
new file mode 100644
index 000000000..6fead2f47
--- /dev/null
+++ b/packages/website/ts/pages/documentation/method_block.tsx
@@ -0,0 +1,174 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {Chip} from 'material-ui/Chip';
+import {colors} from 'material-ui/styles';
+import {
+ TypeDocNode,
+ Styles,
+ TypeDefinitionByName,
+ TypescriptMethod,
+ SolidityMethod,
+ Parameter,
+ HeaderSizes,
+} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {Comment} from 'ts/pages/documentation/comment';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+
+interface MethodBlockProps {
+ method: SolidityMethod|TypescriptMethod;
+ libraryVersion: string;
+ typeDefinitionByName: TypeDefinitionByName;
+}
+
+interface MethodBlockState {
+ shouldShowAnchor: boolean;
+}
+
+const styles: Styles = {
+ chip: {
+ fontSize: 13,
+ backgroundColor: colors.lightBlueA700,
+ color: 'white',
+ height: 11,
+ borderRadius: 14,
+ marginTop: 19,
+ lineHeight: 0.8,
+ },
+};
+
+export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> {
+ constructor(props: MethodBlockProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const method = this.props.method;
+ if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) {
+ return null;
+ }
+
+ return (
+ <div
+ id={method.name}
+ style={{overflow: 'hidden', width: '100%'}}
+ className="pb4"
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ {!method.isConstructor &&
+ <div className="flex">
+ {(method as TypescriptMethod).isStatic &&
+ this.renderChip('Static')
+ }
+ {(method as SolidityMethod).isConstant &&
+ this.renderChip('Constant')
+ }
+ {(method as SolidityMethod).isPayable &&
+ this.renderChip('Payable')
+ }
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={method.name}
+ id={method.name}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </div>
+ }
+ <code className="hljs">
+ <MethodSignature
+ method={method}
+ typeDefinitionByName={this.props.typeDefinitionByName}
+ />
+ </code>
+ {(method as TypescriptMethod).source &&
+ <SourceLink
+ version={this.props.libraryVersion}
+ source={(method as TypescriptMethod).source}
+ />
+ }
+ {method.comment &&
+ <Comment
+ comment={method.comment}
+ className="py2"
+ />
+ }
+ {method.parameters && !_.isEmpty(method.parameters) &&
+ <div>
+ <h4
+ className="pb1 thin"
+ style={{borderBottom: '1px solid #e1e8ed'}}
+ >
+ ARGUMENTS
+ </h4>
+ {this.renderParameterDescriptions(method.parameters)}
+ </div>
+ }
+ {method.returnComment &&
+ <div className="pt1 comment">
+ <h4
+ className="pb1 thin"
+ style={{borderBottom: '1px solid #e1e8ed'}}
+ >
+ RETURNS
+ </h4>
+ <Comment
+ comment={method.returnComment}
+ />
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderChip(text: string) {
+ return (
+ <div
+ className="p1 mr1"
+ style={styles.chip}
+ >
+ {text}
+ </div>
+ );
+ }
+ private renderParameterDescriptions(parameters: Parameter[]) {
+ const descriptions = _.map(parameters, parameter => {
+ const isOptional = parameter.isOptional;
+ return (
+ <div
+ key={`param-description-${parameter.name}`}
+ className="flex pb1 mb2"
+ style={{borderBottom: '1px solid #f0f4f7'}}
+ >
+ <div className="col lg-col-1 md-col-1 sm-hide xs-hide" />
+ <div className="col lg-col-3 md-col-3 sm-col-12 col-12">
+ <div className="bold">
+ {parameter.name}
+ </div>
+ <div className="pt1" style={{color: colors.grey500, fontSize: 14}}>
+ {isOptional && 'optional'}
+ </div>
+ </div>
+ <div className="col lg-col-8 md-col-8 sm-col-12 col-12">
+ {parameter.comment &&
+ <Comment
+ comment={parameter.comment}
+ />
+ }
+ </div>
+ </div>
+ );
+ });
+ return descriptions;
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx
new file mode 100644
index 000000000..3b5d2ce78
--- /dev/null
+++ b/packages/website/ts/pages/documentation/method_signature.tsx
@@ -0,0 +1,62 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {TypescriptMethod, SolidityMethod, TypeDefinitionByName, Parameter} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+
+interface MethodSignatureProps {
+ method: TypescriptMethod|SolidityMethod;
+ shouldHideMethodName?: boolean;
+ shouldUseArrowSyntax?: boolean;
+ typeDefinitionByName?: TypeDefinitionByName;
+}
+
+const defaultProps = {
+ shouldHideMethodName: false,
+ shouldUseArrowSyntax: false,
+};
+
+export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => {
+ const parameters = renderParameters(props.method, props.typeDefinitionByName);
+ const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, ', ', curr];
+ });
+ const methodName = props.shouldHideMethodName ? '' : props.method.name;
+ const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) ?
+ undefined :
+ renderTypeParameter(props.method, props.typeDefinitionByName);
+ return (
+ <span>
+ {props.method.callPath}{methodName}{typeParameterIfExists}({paramString})
+ {props.shouldUseArrowSyntax ? ' => ' : ': '}
+ {' '}
+ {props.method.returnType &&
+ <Type type={props.method.returnType} typeDefinitionByName={props.typeDefinitionByName}/>
+ }
+ </span>
+ );
+};
+
+function renderParameters(method: TypescriptMethod|SolidityMethod, typeDefinitionByName?: TypeDefinitionByName) {
+ const parameters = method.parameters;
+ const params = _.map(parameters, (p: Parameter) => {
+ const isOptional = p.isOptional;
+ return (
+ <span key={`param-${p.type}-${p.name}`}>
+ {p.name}{isOptional && '?'}: <Type type={p.type} typeDefinitionByName={typeDefinitionByName}/>
+ </span>
+ );
+ });
+ return params;
+}
+
+function renderTypeParameter(method: TypescriptMethod, typeDefinitionByName?: TypeDefinitionByName) {
+ const typeParameter = method.typeParameter;
+ const typeParam = (
+ <span>
+ {`<${typeParameter.name} extends `}
+ <Type type={typeParameter.type} typeDefinitionByName={typeDefinitionByName}/>
+ {`>`}
+ </span>
+ );
+ return typeParam;
+}
diff --git a/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx
new file mode 100644
index 000000000..3e97829c4
--- /dev/null
+++ b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx
@@ -0,0 +1,401 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import findVersions = require('find-versions');
+import semverSort = require('semver-sort');
+import {colors} from 'material-ui/styles';
+import CircularProgress from 'material-ui/CircularProgress';
+import {
+ scroller,
+} from 'react-scroll';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ SmartContractsDocSections,
+ Styles,
+ DoxityDocObj,
+ TypeDefinitionByName,
+ DocAgnosticFormat,
+ SolidityMethod,
+ Property,
+ CustomType,
+ MenuSubsectionsBySection,
+ Event,
+ Docs,
+ AddressByContractName,
+ Networks,
+ EtherscanLinkSuffixes,
+} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {utils} from 'ts/utils/utils';
+import {docUtils} from 'ts/utils/doc_utils';
+import {constants} from 'ts/utils/constants';
+import {MethodBlock} from 'ts/pages/documentation/method_block';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {Type} from 'ts/pages/documentation/type';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+import {Comment} from 'ts/pages/documentation/comment';
+import {Badge} from 'ts/components/ui/badge';
+import {EventDefinition} from 'ts/pages/documentation/event_definition';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {doxityUtils} from 'ts/utils/doxity_utils';
+/* tslint:disable:no-var-requires */
+const IntroMarkdown = require('md/docs/smart_contracts/introduction');
+/* tslint:enable:no-var-requires */
+
+const SCROLL_TO_TIMEOUT = 500;
+const CUSTOM_PURPLE = '#690596';
+const CUSTOM_RED = '#e91751';
+const CUSTOM_TURQUOIS = '#058789';
+const DOC_JSON_ROOT = constants.S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT;
+
+const sectionNameToMarkdown = {
+ [SmartContractsDocSections.Introduction]: IntroMarkdown,
+};
+const networkNameToColor: {[network: string]: string} = {
+ [Networks.kovan]: CUSTOM_PURPLE,
+ [Networks.ropsten]: CUSTOM_RED,
+ [Networks.mainnet]: CUSTOM_TURQUOIS,
+};
+
+export interface SmartContractsDocumentationAllProps {
+ source: string;
+ location: Location;
+ dispatcher: Dispatcher;
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface SmartContractsDocumentationState {
+ docAgnosticFormat?: DocAgnosticFormat;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class SmartContractsDocumentation extends
+ React.Component<SmartContractsDocumentationAllProps, SmartContractsDocumentationState> {
+ constructor(props: SmartContractsDocumentationAllProps) {
+ super(props);
+ this.state = {
+ docAgnosticFormat: undefined,
+ };
+ }
+ public componentWillMount() {
+ const pathName = this.props.location.pathname;
+ const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1);
+ const versions = findVersions(lastSegment);
+ const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined;
+ this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
+ ? {}
+ : this.getMenuSubsectionsBySection(this.state.docAgnosticFormat);
+ return (
+ <div>
+ <DocumentTitle title="0x Smart Contract Documentation"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ docsVersion={this.props.docsVersion}
+ availableDocVersions={this.props.availableDocVersions}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ doc={Docs.SmartContracts}
+ />
+ {_.isUndefined(this.state.docAgnosticFormat) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading documentation...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ selectedVersion={this.props.docsVersion}
+ versions={this.props.availableDocVersions}
+ topLevelMenu={constants.menuSmartContracts}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ doc={Docs.SmartContracts}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="smartContractsDocs" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_CONTRACTS_URL} target="_blank">
+ 0x Smart Contracts
+ </a>
+ </h1>
+ {this.renderDocumentation()}
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderDocumentation(): React.ReactNode {
+ const subMenus = _.values(constants.menuSmartContracts);
+ const orderedSectionNames = _.flatten(subMenus);
+ // Since smart contract method params are all base types, no need to pass
+ // down the typeDefinitionByName
+ const typeDefinitionByName = {};
+ const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName));
+
+ return sections;
+ }
+ private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
+ const docSection = this.state.docAgnosticFormat[sectionName];
+
+ const markdownFileIfExists = sectionNameToMarkdown[sectionName];
+ if (!_.isUndefined(markdownFileIfExists)) {
+ return (
+ <MarkdownSection
+ key={`markdown-section-${sectionName}`}
+ sectionName={sectionName}
+ markdownContent={markdownFileIfExists}
+ />
+ );
+ }
+
+ if (_.isUndefined(docSection)) {
+ return null;
+ }
+
+ const sortedProperties = _.sortBy(docSection.properties, 'name');
+ const propertyDefs = _.map(sortedProperties, this.renderProperty.bind(this));
+
+ const sortedMethods = _.sortBy(docSection.methods, 'name');
+ const methodDefs = _.map(sortedMethods, method => {
+ const isConstructor = false;
+ return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName);
+ });
+
+ const sortedEvents = _.sortBy(docSection.events, 'name');
+ const eventDefs = _.map(sortedEvents, (event: Event, i: number) => {
+ return (
+ <EventDefinition
+ key={`event-${event.name}-${i}`}
+ event={event}
+ />
+ );
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <div className="flex">
+ <div style={{marginRight: 7}}>
+ <SectionHeader sectionName={sectionName} />
+ </div>
+ {this.renderNetworkBadges(sectionName)}
+ </div>
+ {docSection.comment &&
+ <Comment
+ comment={docSection.comment}
+ />
+ }
+ {docSection.constructors.length > 0 &&
+ <div>
+ <h2 className="thin">Constructor</h2>
+ {this.renderConstructors(docSection.constructors, typeDefinitionByName)}
+ </div>
+ }
+ {docSection.properties.length > 0 &&
+ <div>
+ <h2 className="thin">Properties</h2>
+ <div>{propertyDefs}</div>
+ </div>
+ }
+ {docSection.methods.length > 0 &&
+ <div>
+ <h2 className="thin">Methods</h2>
+ <div>{methodDefs}</div>
+ </div>
+ }
+ {docSection.events.length > 0 &&
+ <div>
+ <h2 className="thin">Events</h2>
+ <div>{eventDefs}</div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderNetworkBadges(sectionName: string) {
+ const networkToAddressByContractName = constants.contractAddresses[this.props.docsVersion];
+ const badges = _.map(networkToAddressByContractName,
+ (addressByContractName: AddressByContractName, networkName: string) => {
+ const contractAddress = addressByContractName[sectionName];
+ const linkIfExists = utils.getEtherScanLinkIfExists(
+ contractAddress, constants.networkIdByName[networkName], EtherscanLinkSuffixes.address,
+ );
+ return (
+ <a
+ key={`badge-${networkName}-${sectionName}`}
+ href={linkIfExists}
+ target="_blank"
+ style={{color: 'white', textDecoration: 'none'}}
+ >
+ <Badge
+ title={networkName}
+ backgroundColor={networkNameToColor[networkName]}
+ />
+ </a>
+ );
+ });
+ return badges;
+ }
+ private renderConstructors(constructors: SolidityMethod[],
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ const constructorDefs = _.map(constructors, constructor => {
+ return this.renderMethodBlocks(
+ constructor, SmartContractsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName,
+ );
+ });
+ return (
+ <div>
+ {constructorDefs}
+ </div>
+ );
+ }
+ private renderProperty(property: Property): React.ReactNode {
+ return (
+ <div
+ key={`property-${property.name}-${property.type.name}`}
+ className="pb3"
+ >
+ <code className="hljs">
+ {property.name}: <Type type={property.type} />
+ </code>
+ {property.source &&
+ <SourceLink
+ version={this.props.docsVersion}
+ source={property.source}
+ />
+ }
+ {property.comment &&
+ <Comment
+ comment={property.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private renderMethodBlocks(method: SolidityMethod, sectionName: string, isConstructor: boolean,
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ return (
+ <MethodBlock
+ key={`method-${method.name}`}
+ method={method}
+ typeDefinitionByName={typeDefinitionByName}
+ libraryVersion={this.props.docsVersion}
+ />
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = 'smartContractsDocs'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
+ const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
+ if (_.isUndefined(docAgnosticFormat)) {
+ return menuSubsectionsBySection;
+ }
+
+ const docSections = _.keys(SmartContractsDocSections);
+ _.each(docSections, sectionName => {
+ const docSection = docAgnosticFormat[sectionName];
+ if (_.isUndefined(docSection)) {
+ return; // no-op
+ }
+
+ if (sectionName === SmartContractsDocSections.types) {
+ const sortedTypesNames = _.sortBy(docSection.types, 'name');
+ const typeNames = _.map(sortedTypesNames, t => t.name);
+ menuSubsectionsBySection[sectionName] = typeNames;
+ } else {
+ const sortedEventNames = _.sortBy(docSection.events, 'name');
+ const eventNames = _.map(sortedEventNames, m => m.name);
+ const sortedMethodNames = _.sortBy(docSection.methods, 'name');
+ const methodNames = _.map(sortedMethodNames, m => m.name);
+ menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames];
+ }
+ });
+ return menuSubsectionsBySection;
+ }
+ private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> {
+ const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT);
+ const versions = _.keys(versionToFileName);
+ this.props.dispatcher.updateAvailableDocVersions(versions);
+ const sortedVersions = semverSort.desc(versions);
+ const latestVersion = sortedVersions[0];
+
+ let versionToFetch = latestVersion;
+ if (!_.isUndefined(preferredVersionIfExists)) {
+ const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists];
+ if (!_.isUndefined(preferredVersionFileNameIfExists)) {
+ versionToFetch = preferredVersionIfExists;
+ }
+ }
+ this.props.dispatcher.updateCurrentDocsVersion(versionToFetch);
+
+ const versionFileNameToFetch = versionToFileName[versionToFetch];
+ const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT);
+ const docAgnosticFormat = doxityUtils.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj);
+
+ this.setState({
+ docAgnosticFormat,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx
new file mode 100644
index 000000000..2fb69e2f0
--- /dev/null
+++ b/packages/website/ts/pages/documentation/source_link.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {Source} from 'ts/types';
+import {constants} from 'ts/utils/constants';
+
+interface SourceLinkProps {
+ source: Source;
+ version: string;
+}
+
+export function SourceLink(props: SourceLinkProps) {
+ const source = props.source;
+ const githubUrl = constants.GITHUB_0X_JS_URL;
+ const sourceCodeUrl = `${githubUrl}/blob/v${props.version}/${source.fileName}#L${source.line}`;
+ return (
+ <div className="pt2" style={{fontSize: 14}}>
+ <a
+ href={sourceCodeUrl}
+ target="_blank"
+ className="underline"
+ style={{color: colors.grey500}}
+ >
+ Source
+ </a>
+ </div>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx
new file mode 100644
index 000000000..af18f97c2
--- /dev/null
+++ b/packages/website/ts/pages/documentation/type.tsx
@@ -0,0 +1,187 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Link as ScrollLink} from 'react-scroll';
+import * as ReactTooltip from 'react-tooltip';
+import {colors} from 'material-ui/styles';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+import {constants} from 'ts/utils/constants';
+import {Type as TypeDef, TypeDocTypes, TypeDefinitionByName} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+
+const BUILT_IN_TYPE_COLOR = '#e69d00';
+const STRING_LITERAL_COLOR = '#4da24b';
+
+// Some types reference other libraries. For these types, we want to link the user to the relevant documentation.
+const typeToUrl: {[typeName: string]: string} = {
+ Web3: constants.WEB3_DOCS_URL,
+ Provider: constants.WEB3_PROVIDER_DOCS_URL,
+ BigNumber: constants.BIGNUMBERJS_GITHUB_URL,
+};
+
+const typeToSection: {[typeName: string]: string} = {
+ ExchangeWrapper: 'exchange',
+ TokenWrapper: 'token',
+ TokenRegistryWrapper: 'tokenRegistry',
+ EtherTokenWrapper: 'etherToken',
+ ProxyWrapper: 'proxy',
+ TokenTransferProxyWrapper: 'proxy',
+};
+
+interface TypeProps {
+ type: TypeDef;
+ typeDefinitionByName?: TypeDefinitionByName;
+}
+
+// The return type needs to be `any` here so that we can recursively define <Type /> components within
+// <Type /> components (e.g when rendering the union type).
+export function Type(props: TypeProps): any {
+ const type = props.type;
+ const isIntrinsic = type.typeDocType === TypeDocTypes.Intrinsic;
+ const isReference = type.typeDocType === TypeDocTypes.Reference;
+ const isArray = type.typeDocType === TypeDocTypes.Array;
+ const isStringLiteral = type.typeDocType === TypeDocTypes.StringLiteral;
+ let typeNameColor = 'inherit';
+ let typeName: string|React.ReactNode;
+ let typeArgs: React.ReactNode[] = [];
+ switch (type.typeDocType) {
+ case TypeDocTypes.Intrinsic:
+ case TypeDocTypes.Unknown:
+ typeName = type.name;
+ typeNameColor = BUILT_IN_TYPE_COLOR;
+ break;
+
+ case TypeDocTypes.Reference:
+ typeName = type.name;
+ typeArgs = _.map(type.typeArguments, (arg: TypeDef) => {
+ if (arg.typeDocType === TypeDocTypes.Array) {
+ const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`;
+ return (
+ <span>
+ <Type
+ key={key}
+ type={arg.elementType}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />[]
+ </span>
+ );
+ } else {
+ const subType = (
+ <Type
+ key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`}
+ type={arg}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />
+ );
+ return subType;
+ }
+ });
+ break;
+
+ case TypeDocTypes.StringLiteral:
+ typeName = `'${type.value}'`;
+ typeNameColor = STRING_LITERAL_COLOR;
+ break;
+
+ case TypeDocTypes.Array:
+ typeName = type.elementType.name;
+ break;
+
+ case TypeDocTypes.Union:
+ const unionTypes = _.map(type.types, t => {
+ return (
+ <Type
+ key={`type-${t.name}-${t.value}-${t.typeDocType}`}
+ type={t}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />
+ );
+ });
+ typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '|', curr];
+ });
+ break;
+
+ case TypeDocTypes.TypeParameter:
+ typeName = type.name;
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType);
+ }
+ // HACK: Normalize BigNumber to simply BigNumber. For some reason the type
+ // name is unpredictably one or the other.
+ if (typeName === 'BigNumber') {
+ typeName = 'BigNumber';
+ }
+ const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, ', ', curr];
+ });
+
+ const typeNameUrlIfExists = typeToUrl[(typeName as string)];
+ const sectionNameIfExists = typeToSection[(typeName as string)];
+ if (!_.isUndefined(typeNameUrlIfExists)) {
+ typeName = (
+ <a
+ href={typeNameUrlIfExists}
+ target="_blank"
+ className="text-decoration-none"
+ style={{color: colors.lightBlueA700}}
+ >
+ {typeName}
+ </a>
+ );
+ } else if ((isReference || isArray) &&
+ (typeDocUtils.isPublicType(typeName as string) ||
+ !_.isUndefined(sectionNameIfExists))) {
+ const id = Math.random().toString();
+ const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) ? typeName : sectionNameIfExists;
+ let typeDefinition;
+ if (props.typeDefinitionByName) {
+ typeDefinition = props.typeDefinitionByName[typeName as string];
+ }
+ typeName = (
+ <ScrollLink
+ to={typeDefinitionAnchorId}
+ offset={0}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ?
+ <span
+ onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)}
+ style={{color: colors.lightBlueA700, cursor: 'pointer'}}
+ >
+ {typeName}
+ </span> :
+ <span
+ data-tip={true}
+ data-for={id}
+ onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)}
+ style={{color: colors.lightBlueA700, cursor: 'pointer', display: 'inline-block'}}
+ >
+ {typeName}
+ <ReactTooltip
+ type="light"
+ effect="solid"
+ id={id}
+ className="typeTooltip"
+ >
+ <TypeDefinition customType={typeDefinition} shouldAddId={false} />
+ </ReactTooltip>
+ </span>
+ }
+ </ScrollLink>
+ );
+ }
+ return (
+ <span>
+ <span style={{color: typeNameColor}}>{typeName}</span>
+ {isArray && '[]'}{!_.isEmpty(typeArgs) &&
+ <span>
+ {'<'}{commaSeparatedTypeArgs}{'>'}
+ </span>
+ }
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx
new file mode 100644
index 000000000..bcb07be8e
--- /dev/null
+++ b/packages/website/ts/pages/documentation/type_definition.tsx
@@ -0,0 +1,135 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {KindString, CustomType, TypeDocTypes, CustomTypeChild, HeaderSizes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {Interface} from 'ts/pages/documentation/interface';
+import {CustomEnum} from 'ts/pages/documentation/custom_enum';
+import {Enum} from 'ts/pages/documentation/enum';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {Comment} from 'ts/pages/documentation/comment';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+
+const KEYWORD_COLOR = '#a81ca6';
+
+interface TypeDefinitionProps {
+ customType: CustomType;
+ shouldAddId?: boolean;
+}
+
+interface TypeDefinitionState {
+ shouldShowAnchor: boolean;
+}
+
+export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> {
+ public static defaultProps: Partial<TypeDefinitionProps> = {
+ shouldAddId: true,
+ };
+ constructor(props: TypeDefinitionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const customType = this.props.customType;
+ if (!typeDocUtils.isPublicType(customType.name)) {
+ return null; // no-op
+ }
+
+ let typePrefix: string;
+ let codeSnippet: React.ReactNode;
+ switch (customType.kindString) {
+ case KindString.Interface:
+ typePrefix = 'Interface';
+ codeSnippet = (
+ <Interface
+ type={customType}
+ />
+ );
+ break;
+
+ case KindString.Variable:
+ typePrefix = 'Enum';
+ codeSnippet = (
+ <CustomEnum
+ type={customType}
+ />
+ );
+ break;
+
+ case KindString.Enumeration:
+ typePrefix = 'Enum';
+ const enumValues = _.map(customType.children, (c: CustomTypeChild) => {
+ return {
+ name: c.name,
+ defaultValue: c.defaultValue,
+ };
+ });
+ codeSnippet = (
+ <Enum
+ values={enumValues}
+ />
+ );
+ break;
+
+ case KindString['Type alias']:
+ typePrefix = 'Type Alias';
+ codeSnippet = (
+ <span>
+ <span style={{color: KEYWORD_COLOR}}>type</span> {customType.name} ={' '}
+ {customType.type.typeDocType !== TypeDocTypes.Reflection ?
+ <Type type={customType.type} /> :
+ <MethodSignature
+ method={customType.type.method}
+ shouldHideMethodName={true}
+ shouldUseArrowSyntax={true}
+ />
+ }
+ </span>
+ );
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('type.kindString', customType.kindString);
+ }
+
+ const typeDefinitionAnchorId = customType.name;
+ return (
+ <div
+ id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
+ className="pb2"
+ style={{overflow: 'hidden', width: '100%'}}
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={`${typePrefix} ${customType.name}`}
+ id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ <div style={{fontSize: 16}}>
+ <pre>
+ <code className="hljs">
+ {codeSnippet}
+ </code>
+ </pre>
+ </div>
+ {customType.comment &&
+ <Comment
+ comment={customType.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx
new file mode 100644
index 000000000..c26fb7d0a
--- /dev/null
+++ b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx
@@ -0,0 +1,340 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import DocumentTitle = require('react-document-title');
+import findVersions = require('find-versions');
+import semverSort = require('semver-sort');
+import {colors} from 'material-ui/styles';
+import MenuItem from 'material-ui/MenuItem';
+import CircularProgress from 'material-ui/CircularProgress';
+import Paper from 'material-ui/Paper';
+import {
+ Link as ScrollLink,
+ Element as ScrollElement,
+ scroller,
+} from 'react-scroll';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ KindString,
+ TypeDocNode,
+ ZeroExJsDocSections,
+ Styles,
+ ScreenWidths,
+ TypeDefinitionByName,
+ DocAgnosticFormat,
+ TypescriptMethod,
+ Property,
+ CustomType,
+ Docs,
+} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {utils} from 'ts/utils/utils';
+import {docUtils} from 'ts/utils/doc_utils';
+import {constants} from 'ts/utils/constants';
+import {Loading} from 'ts/components/ui/loading';
+import {MethodBlock} from 'ts/pages/documentation/method_block';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {Type} from 'ts/pages/documentation/type';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+import {Comment} from 'ts/pages/documentation/comment';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+/* tslint:disable:no-var-requires */
+const IntroMarkdown = require('md/docs/0xjs/introduction');
+const InstallationMarkdown = require('md/docs/0xjs/installation');
+const AsyncMarkdown = require('md/docs/0xjs/async');
+const ErrorsMarkdown = require('md/docs/0xjs/errors');
+const versioningMarkdown = require('md/docs/0xjs/versioning');
+/* tslint:enable:no-var-requires */
+
+const SCROLL_TO_TIMEOUT = 500;
+const DOC_JSON_ROOT = constants.S3_0XJS_DOCUMENTATION_JSON_ROOT;
+
+const sectionNameToMarkdown = {
+ [ZeroExJsDocSections.introduction]: IntroMarkdown,
+ [ZeroExJsDocSections.installation]: InstallationMarkdown,
+ [ZeroExJsDocSections.async]: AsyncMarkdown,
+ [ZeroExJsDocSections.errors]: ErrorsMarkdown,
+ [ZeroExJsDocSections.versioning]: versioningMarkdown,
+};
+
+export interface ZeroExJSDocumentationPassedProps {
+ source: string;
+ location: Location;
+}
+
+export interface ZeroExJSDocumentationAllProps {
+ source: string;
+ location: Location;
+ dispatcher: Dispatcher;
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface ZeroExJSDocumentationState {
+ docAgnosticFormat?: DocAgnosticFormat;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class ZeroExJSDocumentation extends React.Component<ZeroExJSDocumentationAllProps, ZeroExJSDocumentationState> {
+ constructor(props: ZeroExJSDocumentationAllProps) {
+ super(props);
+ this.state = {
+ docAgnosticFormat: undefined,
+ };
+ }
+ public componentWillMount() {
+ const pathName = this.props.location.pathname;
+ const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1);
+ const versions = findVersions(lastSegment);
+ const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined;
+ this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
+ ? {}
+ : typeDocUtils.getMenuSubsectionsBySection(this.state.docAgnosticFormat);
+ return (
+ <div>
+ <DocumentTitle title="0x.js Documentation"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ docsVersion={this.props.docsVersion}
+ availableDocVersions={this.props.availableDocVersions}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ doc={Docs.ZeroExJs}
+ />
+ {_.isUndefined(this.state.docAgnosticFormat) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading documentation...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ selectedVersion={this.props.docsVersion}
+ versions={this.props.availableDocVersions}
+ topLevelMenu={typeDocUtils.getFinal0xjsMenu(this.props.docsVersion)}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ doc={Docs.ZeroExJs}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="zeroExJSDocs" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_0X_JS_URL} target="_blank">
+ 0x.js
+ </a>
+ </h1>
+ {this.renderDocumentation()}
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderDocumentation(): React.ReactNode {
+ const typeDocSection = this.state.docAgnosticFormat[ZeroExJsDocSections.types];
+ const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name');
+
+ const subMenus = _.values(constants.menu0xjs);
+ const orderedSectionNames = _.flatten(subMenus);
+ const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName));
+
+ return sections;
+ }
+ private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
+ const docSection = this.state.docAgnosticFormat[sectionName];
+
+ const markdownFileIfExists = sectionNameToMarkdown[sectionName];
+ if (!_.isUndefined(markdownFileIfExists)) {
+ return (
+ <MarkdownSection
+ key={`markdown-section-${sectionName}`}
+ sectionName={sectionName}
+ markdownContent={markdownFileIfExists}
+ />
+ );
+ }
+
+ if (_.isUndefined(docSection)) {
+ return null;
+ }
+
+ const typeDefs = _.map(docSection.types, customType => {
+ return (
+ <TypeDefinition
+ key={`type-${customType.name}`}
+ customType={customType}
+ />
+ );
+ });
+ const propertyDefs = _.map(docSection.properties, this.renderProperty.bind(this));
+ const methodDefs = _.map(docSection.methods, method => {
+ const isConstructor = false;
+ return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName);
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <SectionHeader sectionName={sectionName} />
+ <Comment
+ comment={docSection.comment}
+ />
+ {sectionName === ZeroExJsDocSections.zeroEx && docSection.constructors.length > 0 &&
+ <div>
+ <h2 className="thin">Constructor</h2>
+ {this.renderZeroExConstructors(docSection.constructors, typeDefinitionByName)}
+ </div>
+ }
+ {docSection.properties.length > 0 &&
+ <div>
+ <h2 className="thin">Properties</h2>
+ <div>{propertyDefs}</div>
+ </div>
+ }
+ {docSection.methods.length > 0 &&
+ <div>
+ <h2 className="thin">Methods</h2>
+ <div>{methodDefs}</div>
+ </div>
+ }
+ {typeDefs.length > 0 &&
+ <div>
+ <div>{typeDefs}</div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderZeroExConstructors(constructors: TypescriptMethod[],
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ const constructorDefs = _.map(constructors, constructor => {
+ return this.renderMethodBlocks(
+ constructor, ZeroExJsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName,
+ );
+ });
+ return (
+ <div>
+ {constructorDefs}
+ </div>
+ );
+ }
+ private renderProperty(property: Property): React.ReactNode {
+ return (
+ <div
+ key={`property-${property.name}-${property.type.name}`}
+ className="pb3"
+ >
+ <code className="hljs">
+ {property.name}: <Type type={property.type} />
+ </code>
+ <SourceLink
+ version={this.props.docsVersion}
+ source={property.source}
+ />
+ {property.comment &&
+ <Comment
+ comment={property.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private renderMethodBlocks(method: TypescriptMethod, sectionName: string, isConstructor: boolean,
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ return (
+ <MethodBlock
+ key={`method-${method.name}-${!_.isUndefined(method.source) ? method.source.line : ''}`}
+ method={method}
+ typeDefinitionByName={typeDefinitionByName}
+ libraryVersion={this.props.docsVersion}
+ />
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = 'zeroExJSDocs'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> {
+ const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT);
+ const versions = _.keys(versionToFileName);
+ this.props.dispatcher.updateAvailableDocVersions(versions);
+ const sortedVersions = semverSort.desc(versions);
+ const latestVersion = sortedVersions[0];
+
+ let versionToFetch = latestVersion;
+ if (!_.isUndefined(preferredVersionIfExists)) {
+ const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists];
+ if (!_.isUndefined(preferredVersionFileNameIfExists)) {
+ versionToFetch = preferredVersionIfExists;
+ }
+ }
+ this.props.dispatcher.updateCurrentDocsVersion(versionToFetch);
+
+ const versionFileNameToFetch = versionToFileName[versionToFetch];
+ const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT);
+ const docAgnosticFormat = typeDocUtils.convertToDocAgnosticFormat((versionDocObj as TypeDocNode));
+
+ this.setState({
+ docAgnosticFormat,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+}
diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx
new file mode 100644
index 000000000..3c65d1042
--- /dev/null
+++ b/packages/website/ts/pages/faq/faq.tsx
@@ -0,0 +1,497 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {Styles, FAQSection, FAQQuestion, WebsitePaths} from 'ts/types';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+import {Question} from 'ts/pages/faq/question';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+
+export interface FAQProps {
+ source: string;
+ location: Location;
+}
+
+interface FAQState {}
+
+const styles: Styles = {
+ thin: {
+ fontWeight: 100,
+ },
+};
+
+const sections: FAQSection[] = [
+ {
+ name: '0x Protocol',
+ questions: [
+ {
+ prompt: 'What is 0x?',
+ answer: (
+ <div>
+ At its core, 0x is an open and non-rent seeking protocol that facilitates trustless,
+ low friction exchange of Ethereum-based assets. Developers can use 0x as a platform
+ to build exchange applications on top of{' '}
+ (<a href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} target="blank">0x.js</a>
+ {' '}is a Javascript library for interacting with the 0x protocol). For end users, 0x will be
+ the infrastructure of a wide variety of user-facing applications i.e.{' '}
+ <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank">0x Portal</a>,
+ a decentralized application that facilitates trustless trading of Ethereum-based tokens
+ between known counterparties.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What problem does 0x solve?',
+ answer: (
+ <div>
+ In the two years since the Ethereum blockchain’s genesis block, numerous decentralized
+ applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange.
+ Rapid iteration and a lack of best practices have left the blockchain scattered with
+ proprietary and application-specific implementations. As a result, end users are
+ exposed to numerous smart contracts of varying quality and security, with unique
+ configuration processes and learning curves, all of which implement the same
+ functionality. This approach imposes unnecessary costs on the network by fragmenting
+ end users according to the particular dApp each user happens to be using, eliminating
+ valuable network effects around liquidity. 0x is the solution to this problem by
+ acting as modular, unopinionated building blocks that may be assembled and reconfigured.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?',
+ answer: (
+ <div>
+ <ul>
+ <li>
+ 0x is a protocol for exchange, not a user-facing exchange application.
+ </li>
+ <li>
+ 0x is decentralized and trustless; there is no central party which can be
+ hacked, run away with customer funds or be subjected to government regulations.
+ Hacks of Mt. Gox, Shapeshift and Bitfinex have demonstrated that these types of
+ systemic risks are palpable.
+ </li>
+ <li>
+ Rather than a proprietary system that exists to extract rent for its owners,
+ 0x is public infrastructure that is funded by a globally distributed community
+ of stakeholders. While the protocol is free to use, it enables for-profit
+ user-facing exchange applications to be built on top of the protocol.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'If 0x protocol is free to use, where do transaction fees come in?',
+ answer: (
+ <div>
+ 0x protocol uses off-chain order books to massively reduce friction costs for
+ market makers and ensure that the blockchain is only used for trade settlement.
+ Hosting and maintaining an off-chain order book is a service; to incent “Relayers”
+ to provide this service they must be able to charge transaction fees on trading
+ activity. Relayers are free to set their transaction fees to any value they desire.
+ We expect Relayers to be highly competitive and transaction fees to approach an
+ efficient economic equilibrium over time.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What are the differences between 0x protocol and state channels?',
+ answer: (
+ <div>
+ <div>
+ Participants in a state channel pass cryptographically signed messages back and
+ forth, accumulating intermediate state changes without publishing them to the
+ canonical chain until the channel is closed. State channels are ideal for “bar tab”
+ applications where numerous intermediate state changes may be accumulated off-chain
+ before being settled by a final on-chain transaction (i.e. day trading, poker,
+ turn-based games).
+ </div>
+ <ul>
+ <li>
+ While state channels drastically reduce the number of on-chain transactions
+ for specific use cases, numerous on-chain transactions and a security deposit
+ are required to open and safely close a state channel making them less efficient
+ than 0x for executing one-time trades.
+ </li>
+ <li>
+ State channels are isolated from the Ethereum blockchain meaning that
+ they cannot interact with smart contracts. 0x is designed to be integrated
+ directly into smart contracts so trades can be executed programmatically
+ in a single line of Solidity code.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'What types of digital assets are supported by 0x?',
+ answer: (
+ <div>
+ 0x supports all Ethereum-based assets that adhere to the ERC20 token standard.
+ There are many ERC20 tokens, worth a combined $2.2B, and more tokens are created
+ each month. We believe that, by 2020, thousands of assets will be tokenized and
+ moved onto the Ethereum blockchain including traditional securities such as equities,
+ bonds and derivatives, fiat currencies and scarce digital goods such as video game
+ items. In the future, cross-blockchain solutions such as{' '}
+ <a href="https://cosmos.network/" target="_blank">Cosmos</a> and{' '}
+ <a href="http://polkadot.io/" target="_blank">Polkadot</a> will allow cryptocurrencies
+ to freely move between blockchains and, naturally, currencies such as Bitcoin will
+ end up being represented as ERC20 tokens on the Ethereum blockchain.
+ </div>
+ ),
+ },
+ {
+ prompt: '0x is open source: what prevents someone from forking the protocol?',
+ answer: (
+ <div>
+ Ethereum and Bitcoin are both open source protocols. Each protocol has been forked,
+ but the resulting clone networks have seen little adoption (as measured by transaction
+ count or market cap). This is because users have little to no incentive to switch
+ over to a clone network if the original has initial network effects and a talented
+ developer team behind it.
+ An exception is in the case that a protocol includes a controversial feature such as
+ a method of rent extraction or a monetary policy that favors one group of users over
+ another (Zcash developer subsidy - for better or worse - resulted in Zclassic).
+ Perceived inequality can provide a strong enough incentive that users will fork the
+ original protocol’s codebase and spin up a new network that eliminates the controversial
+ feature. In the case of 0x, there is no rent extraction and no users are given
+ special permissions.
+
+ 0x protocol is upgradable. Cutting-edge technical capabilities can be integrated
+ into 0x via decentralized governance (see section below), eliminating incentives
+ to fork off of the original protocol and sacrifice the network effects surrounding
+ liquidity that result from the shared protocol and settlement layer.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: '0x Token (ZRX)',
+ questions: [
+ {
+ prompt: 'Explain how the 0x protocol token (zrx) works.',
+ answer: (
+ <div>
+ <div>
+ 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '}
+ <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank">
+ coordination problem
+ </a> and drive network effects around liquidity, creating a feedback loop
+ where early adopters of the protocol benefit from wider adoption and 2) to
+ be used for decentralized governance over 0x protocol's update mechanism.
+ In more detail:
+ </div>
+ <ul>
+ <li>
+ ZRX tokens are used by Makers and Takers (market participants that generate
+ and consume orders, respectively) to pay transaction fees to Relayers
+ (entities that host and maintain public order books).
+ </li>
+ <li>
+ ZRX tokens are used for decentralized governance over 0x protocol’s update
+ mechanism which allows its underlying smart contracts to be replaced and
+ improved over time. An update mechanism is needed because 0x is built upon
+ Ethereum’s rapidly evolving technology stack, decentralized governance is
+ needed because 0x protocol’s smart contracts will have access to user funds
+ and numerous dApps will need to plug into 0x smart contracts. Decentralized
+ governance ensures that this update process is secure and minimizes disruption
+ to the network.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?',
+ answer: (
+ <div>
+ 0x protocol’s decentralized update mechanism is analogous to proof-of-stake.
+ To maximize the alignment of stakeholder and end user incentives, the staked
+ asset must provide utility within the protocol.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How will decentralized governance work?',
+ answer: (
+ <div>
+ Decentralized governance is an ongoing focus of research; it will involve token
+ voting with ZRX. Ultimately the solution will maximize security while also maximizing
+ the protocol’s ability to absorb new innovations. Until the governance structure is
+ formalized and encoded within 0x DAO, a multi-sig will be used as a placeholder.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: 'ZRX Token Launch and Fund Use',
+ questions: [
+ {
+ prompt: 'What is the total supply of ZRX tokens?',
+ answer: (
+ <div>
+ 1,000,000,000 ZRX. Fixed supply.
+ </div>
+ ),
+ },
+ {
+ prompt: 'When is the Token Launch? will there be a pre-sale?',
+ answer: (
+ <div>
+ The token launch will be on August 15th, 2017. There will not be a pre-sale.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What will the token launch proceeds be used for?',
+ answer: (
+ <div>
+ 100% of the proceeds raised in the token launch will be used to fund the development
+ of free and open source software, tools and infrastructure that support the protocol
+ and surrounding ecosystem. Check out our{' '}
+ <a
+ href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY"
+ target="_blank"
+ >
+ development roadmap
+ </a>.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What will be the initial distribution of ZRX tokens?',
+ answer: (
+ <div>
+ <div className="center" style={{width: '100%'}}>
+ <img
+ style={{width: 350}}
+ src="/images/zrx_pie_chart.png"
+ />
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Token Launch (50%)
+ </div>
+ <div>
+ ZRX is inherently a governance token that plays a critical role in the
+ process of upgrading 0x protocol. We are fully committed to formulating
+ a functional and theoretically sound governance model and we plan to dedicate
+ significant resources to R&D.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Retained by 0x (15%)
+ </div>
+ <div>
+ The 0x core development team will be able to sustain itself for approximately
+ five years using funds raised through the token launch. If 0x protocol
+ proves to be as foundational a technology as we believe it to be, the
+ retained ZRX tokens will allow the 0x core development team to sustain
+ operations beyond the first 5 years.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Developer Fund (15%)
+ </div>
+ <div>
+ The Developer Fund will be used to make targeted capital injections
+ into high potential projects and teams that are attempting to grow
+ the 0x ecosystem, strategic partnerships, hackathon prizes and community
+ development activities.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Founding Team (10%)
+ </div>
+ <div>
+ The founding team’s allocation of ZRX will vest over a traditional 4
+ year vesting schedule with a one year cliff. We believe this should
+ be standard practice for any team that is committed to making their
+ project a long term success.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Early Backers & Advisors (10%)
+ </div>
+ <div>
+ Our backers and advisors have provided capital, resources and guidance
+ that have allowed us to fill out our team, setup a robust legal entity
+ and build a fully functional product before launching a token. As a result,
+ we have a proven track record and can offer a token that holds genuine utility.
+ </div>
+ </div>
+ </div>
+ ),
+ },
+ {
+ prompt: 'Can I mine ZRX tokens?',
+ answer: (
+ <div>
+ No, the total supply of ZRX tokens is fixed and there is no continuous issuance
+ model. Users that facilitate trading over 0x protocol by operating a Relayer
+ earn transaction fees denominated in ZRX; as more trading activity is generated,
+ more transaction fees are earned.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?',
+ answer: (
+ <div>
+ No, ZRX tokens sold in the token launch will immediately be liquid.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Will there be a lockup period for tokens allocated to the founding team?',
+ answer: (
+ <div>
+ Yes. ZRX tokens allocated to founders, advisors and staff members will be released
+ over a 4 year vesting schedule with a 25% cliff upon completion of the initial
+ token launch and 25% released each subsequent year in monthly installments. Staff
+ members hired after the token launch will have a 4 year vesting schedule with a
+ one year cliff.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Which cryptocurrencies will be accepted in the token launch?',
+ answer: (
+ <div>ETH.</div>
+ ),
+ },
+ {
+ prompt: 'When will 0x be live?',
+ answer: (
+ <div>
+ An alpha version of 0x has been live on our private test network since January
+ 2017. Version 1.0 of 0x protocol will be deployed to the canonical Ethereum
+ blockchain after a round of security audits and prior to the public token launch.
+ 0x will be using the 0x protocol during our token launch.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Where can I find a development roadmap?',
+ answer: (
+ <div>
+ Check it out{' '}
+ <a
+ href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo"
+ target="_blank"
+ >
+ here
+ </a>.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: 'Team',
+ questions: [
+ {
+ prompt: 'Where is 0x based?',
+ answer: (
+ <div>
+ 0x was founded in SF and is driven by a diverse group of contributors.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How can I get involved?',
+ answer: (
+ <div>
+ Join our <a href={constants.ZEROEX_CHAT_URL} target="_blank">Rocket.chat</a>!
+ As an open source project, 0x will rely on a worldwide community of passionate
+ developers to contribute proposals, ideas and code.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Why the name 0x?',
+ answer: (
+ <div>
+ 0x is the prefix for hexadecimal numeric constants including Ethereum addresses.
+ In a more abstract context, as the first open protocol for exchange 0x represents
+ the beginning of the end for the exchange industry’s rent seeking oligopoly:
+ zero exchange.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How do you pronounce 0x?',
+ answer: (
+ <div>
+ We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.
+ </div>
+ ),
+ },
+ ],
+ },
+];
+
+export class FAQ extends React.Component<FAQProps, FAQState> {
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div>
+ <DocumentTitle title="0x FAQ"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ />
+ <div
+ id="faq"
+ className="mx-auto max-width-4 pt4"
+ style={{color: colors.grey800}}
+ >
+ <h1 className="center" style={{...styles.thin}}>0x FAQ</h1>
+ <div className="sm-px2 md-px2 lg-px0 pb4">
+ {this.renderSections()}
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderSections() {
+ const renderedSections = _.map(sections, (section: FAQSection, i: number) => {
+ const isFirstSection = i === 0;
+ return (
+ <div key={section.name}>
+ <h3>{section.name}</h3>
+ {this.renderQuestions(section.questions, isFirstSection)}
+ </div>
+ );
+ });
+ return renderedSections;
+ }
+ private renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) {
+ const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => {
+ const isFirstQuestion = i === 0;
+ return (
+ <Question
+ key={question.prompt}
+ prompt={question.prompt}
+ answer={question.answer}
+ shouldDisplayExpanded={isFirstSection && isFirstQuestion}
+ />
+ );
+ });
+ return renderedQuestions;
+ }
+}
diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx
new file mode 100644
index 000000000..4ed198b91
--- /dev/null
+++ b/packages/website/ts/pages/faq/question.tsx
@@ -0,0 +1,52 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Card, CardHeader, CardText} from 'material-ui/Card';
+
+export interface QuestionProps {
+ prompt: string;
+ answer: React.ReactNode;
+ shouldDisplayExpanded: boolean;
+}
+
+interface QuestionState {
+ isExpanded: boolean;
+}
+
+export class Question extends React.Component<QuestionProps, QuestionState> {
+ constructor(props: QuestionProps) {
+ super(props);
+ this.state = {
+ isExpanded: props.shouldDisplayExpanded,
+ };
+ }
+ public render() {
+ return (
+ <div
+ className="py1"
+ >
+ <Card
+ initiallyExpanded={this.props.shouldDisplayExpanded}
+ onExpandChange={this.onExchangeChange.bind(this)}
+ >
+ <CardHeader
+ title={this.props.prompt}
+ style={{borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none'}}
+ titleStyle={{color: 'rgb(66, 66, 66)'}}
+ actAsExpander={true}
+ showExpandableButton={true}
+ />
+ <CardText expandable={true}>
+ <div style={{lineHeight: 1.4}}>
+ {this.props.answer}
+ </div>
+ </CardText>
+ </Card>
+ </div>
+ );
+ }
+ private onExchangeChange() {
+ this.setState({
+ isExpanded: !this.state.isExpanded,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx
new file mode 100644
index 000000000..32ea86736
--- /dev/null
+++ b/packages/website/ts/pages/landing/landing.tsx
@@ -0,0 +1,843 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import {Link} from 'react-router-dom';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {Styles, WebsitePaths, ScreenWidths} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {TopBar} from 'ts/components/top_bar';
+import {Footer} from 'ts/components/footer';
+
+interface BoxContent {
+ title: string;
+ description: string;
+ imageUrl: string;
+ classNames: string;
+}
+interface AssetType {
+ title: string;
+ imageUrl: string;
+ style?: React.CSSProperties;
+}
+interface UseCase {
+ imageUrl: string;
+ type: string;
+ description: string;
+ classNames: string;
+ style?: React.CSSProperties;
+ projectIconUrls: string[];
+}
+interface Project {
+ logoFileName: string;
+ projectUrl: string;
+}
+
+const THROTTLE_TIMEOUT = 100;
+const CUSTOM_HERO_BACKGROUND_COLOR = '#404040';
+const CUSTOM_PROJECTS_BACKGROUND_COLOR = '#343333';
+const CUSTOM_WHITE_BACKGROUND = 'rgb(245, 245, 245)';
+const CUSTOM_WHITE_TEXT = '#E4E4E4';
+const CUSTOM_GRAY_TEXT = '#919191';
+
+const boxContents: BoxContent[] = [
+ {
+ title: 'Trustless exchange',
+ description: 'Built on Ethereum\'s distributed network with no centralized \
+ point of failure and no down time, each trade is settled atomically \
+ and without counterparty risk.',
+ imageUrl: '/images/landing/distributed_network.png',
+ classNames: '',
+ },
+ {
+ title: 'Shared liquidity',
+ description: 'By sharing a standard API, relayers can easily aggregate liquidity pools, \
+ creating network effects around liquidity that compound as more relayers come online.',
+ imageUrl: '/images/landing/liquidity.png',
+ classNames: 'mx-auto',
+ },
+ {
+ title: 'Open source',
+ description: '0x is open source, permissionless and free to use. Trade directly with a known \
+ counterparty for free or pay a relayer some ZRX tokens to access their liquidity \
+ pool.',
+ imageUrl: '/images/landing/open_source.png',
+ classNames: 'right',
+ },
+];
+
+const projects: Project[] = [
+ {
+ logoFileName: 'ethfinex-top.png',
+ projectUrl: constants.ETHFINEX_URL,
+ },
+ {
+ logoFileName: 'radar_relay_top.png',
+ projectUrl: constants.RADAR_RELAY_URL,
+ },
+ {
+ logoFileName: 'paradex_top.png',
+ projectUrl: constants.PARADEX_URL,
+ },
+ {
+ logoFileName: 'the_ocean.png',
+ projectUrl: constants.OCEAN_URL,
+ },
+ {
+ logoFileName: 'dydx.png',
+ projectUrl: constants.DYDX_URL,
+ },
+ {
+ logoFileName: 'melonport.png',
+ projectUrl: constants.MELONPORT_URL,
+ },
+ {
+ logoFileName: 'maker.png',
+ projectUrl: constants.MAKER_URL,
+ },
+ {
+ logoFileName: 'dharma.png',
+ projectUrl: constants.DHARMA_URL,
+ },
+ {
+ logoFileName: 'lendroid.png',
+ projectUrl: constants.LENDROID_URL,
+ },
+ {
+ logoFileName: 'district0x.png',
+ projectUrl: constants.DISTRICT_0X_URL,
+ },
+ {
+ logoFileName: 'aragon.png',
+ projectUrl: constants.ARAGON_URL,
+ },
+ {
+ logoFileName: 'blocknet.png',
+ projectUrl: constants.BLOCKNET_URL,
+ },
+ {
+ logoFileName: 'status.png',
+ projectUrl: constants.STATUS_URL,
+ },
+ {
+ logoFileName: 'augur.png',
+ projectUrl: constants.AUGUR_URL,
+ },
+ {
+ logoFileName: 'anx.png',
+ projectUrl: constants.OPEN_ANX_URL,
+ },
+ {
+ logoFileName: 'auctus.png',
+ projectUrl: constants.AUCTUS_URL,
+ },
+];
+
+export interface LandingProps {
+ location: Location;
+}
+
+interface LandingState {
+ screenWidth: ScreenWidths;
+}
+
+export class Landing extends React.Component<LandingProps, LandingState> {
+ private throttledScreenWidthUpdate: () => void;
+ constructor(props: LandingProps) {
+ super(props);
+ this.state = {
+ screenWidth: utils.getScreenWidth(),
+ };
+ this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
+ }
+ public componentDidMount() {
+ window.addEventListener('resize', this.throttledScreenWidthUpdate);
+ window.scrollTo(0, 0);
+ }
+ public componentWillUnmount() {
+ window.removeEventListener('resize', this.throttledScreenWidthUpdate);
+ }
+ public render() {
+ return (
+ <div id="landing" className="clearfix" style={{color: colors.grey800}}>
+ <DocumentTitle title="0x Protocol"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ isNightVersion={true}
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR, position: 'relative'}}
+ />
+ {this.renderHero()}
+ {this.renderProjects()}
+ {this.renderTokenizationSection()}
+ {this.renderProtocolSection()}
+ {this.renderInfoBoxes()}
+ {this.renderBuildingBlocksSection()}
+ {this.renderUseCases()}
+ {this.renderCallToAction()}
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderHero() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const buttonLabelStyle: React.CSSProperties = {
+ textTransform: 'none',
+ fontSize: isSmallScreen ? 12 : 14,
+ fontWeight: 400,
+ };
+ const lightButtonStyle: React.CSSProperties = {
+ borderRadius: 6,
+ border: '1px solid #D8D8D8',
+ lineHeight: '33px',
+ height: 38,
+ };
+ const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center';
+ return (
+ <div
+ className="clearfix py4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto max-width-4 clearfix"
+ >
+ <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix">
+ <div className="col lg-col-5 md-col-5 col-12 sm-center">
+ <img
+ src="/images/landing/hero_chip_image.png"
+ height={isSmallScreen ? 300 : 395}
+ />
+ </div>
+ <div
+ className={left}
+ style={{color: 'white'}}
+ >
+ <div style={{paddingLeft: isSmallScreen ? 0 : 12}}>
+ <div
+ className="sm-pb2"
+ style={{fontFamily: 'Roboto Mono', fontSize: isSmallScreen ? 26 : 34}}
+ >
+ Powering decentralized exchange
+ </div>
+ <div
+ className="pt2 h5 sm-mx-auto"
+ style={{maxWidth: 446, fontFamily: 'Roboto Mono', lineHeight: 1.7, fontWeight: 300}}
+ >
+ 0x is an open, permissionless protocol allowing for ERC20 tokens to
+ be traded on the Ethereum blockchain.
+ </div>
+ <div className="pt3 clearfix sm-mx-auto" style={{maxWidth: 342}}>
+ <div className="lg-pr2 md-pr2 col col-6 sm-center">
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 157.36}}
+ buttonStyle={{borderRadius: 6}}
+ labelStyle={buttonLabelStyle}
+ label="Build on 0x"
+ onClick={_.noop}
+ />
+ </Link>
+ </div>
+ <div className="col col-6 sm-center">
+ <a
+ href={constants.ZEROEX_CHAT_URL}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 150}}
+ buttonStyle={lightButtonStyle}
+ labelColor="white"
+ backgroundColor={CUSTOM_HERO_BACKGROUND_COLOR}
+ labelStyle={buttonLabelStyle}
+ label="Join the community"
+ onClick={_.noop}
+ />
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderProjects() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const isMediumScreen = this.state.screenWidth === ScreenWidths.MD;
+ const projectList = _.map(projects, (project: Project, i: number) => {
+ const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - (i % 2);
+ return (
+ <div
+ key={`project-${project.logoFileName}`}
+ className={`col col-${colWidth} center`}
+ >
+ <div>
+ <a
+ href={project.projectUrl}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <img
+ src={`/images/landing/project_logos/${project.logoFileName}`}
+ height={isSmallScreen ? 60 : 92}
+ />
+ </a>
+ </div>
+ </div>
+ );
+ });
+ const titleStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ color: '#A4A4A4',
+ textTransform: 'uppercase',
+ fontWeight: 300,
+ letterSpacing: 3,
+ };
+ return (
+ <div
+ className="clearfix py4"
+ style={{backgroundColor: CUSTOM_PROJECTS_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 clearfix sm-px3">
+ <div
+ className="h4 pb3 md-pl3 sm-pl2"
+ style={titleStyle}
+ >
+ Projects building on 0x
+ </div>
+ <div className="clearfix">
+ {projectList}
+ </div>
+ <div
+ className="pt3 mx-auto center"
+ style={{color: CUSTOM_GRAY_TEXT, fontFamily: 'Roboto Mono', maxWidth: 300, fontSize: 14}}
+ >
+ view the{' '}
+ <Link
+ to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_GRAY_TEXT}}
+ >
+ full list
+ </Link>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderTokenizationSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div
+ className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2"
+ style={{backgroundColor: CUSTOM_WHITE_BACKGROUND}}
+ >
+ <div className="mx-auto max-width-4 py4 clearfix">
+ {isSmallScreen &&
+ this.renderTokenCloud()
+ }
+ <div className="col lg-col-6 md-col-6 col-12">
+ <div className="mx-auto" style={{maxWidth: 385, paddingTop: 7}}>
+ <div
+ className="lg-h1 md-h1 sm-h2 sm-center sm-pt3"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ The world's value is becoming tokenized
+ </div>
+ <div
+ className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center"
+ style={{fontFamily: 'Roboto Mono', lineHeight: 1.7}}
+ >
+ {isSmallScreen ?
+ <span>
+ The Ethereum blockchain is an open, borderless financial system that represents
+ a wide variety of assets as cryptographic tokens. In the future, most digital
+ assets and goods will be tokenized.
+ </span> :
+ <div>
+ <div>
+ The Ethereum blockchain is an open, borderless
+ financial system that represents
+ </div>
+ <div>
+ a wide variety of assets as cryptographic tokens.
+ In the future, most digital assets and goods will be tokenized.
+ </div>
+ </div>
+ }
+ </div>
+ <div className="flex pt1 sm-px3">
+ {this.renderAssetTypes()}
+ </div>
+ </div>
+ </div>
+ {!isSmallScreen &&
+ this.renderTokenCloud()
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderProtocolSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div
+ className="clearfix lg-py4 md-py4 sm-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix">
+ <div className="col lg-col-6 md-col-6 col-12 sm-center">
+ <img
+ src="/images/landing/relayer_diagram.png"
+ height={isSmallScreen ? 326 : 426}
+ />
+ </div>
+ <div
+ className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto"
+ style={{color: CUSTOM_WHITE_TEXT, paddingTop: 8, maxWidth: isSmallScreen ? 'none' : 445}}
+ >
+ <div
+ className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ <div>
+ Off-chain order relay
+ </div>
+ <div>
+ On-chain settlement
+ </div>
+ </div>
+ <div
+ className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto"
+ style={{fontFamily: 'Roboto Mono', lineHeight: 1.7, fontWeight: 300, maxWidth: 445}}
+ >
+ In 0x protocol, orders are transported off-chain, massively reducing gas
+ costs and eliminating blockchain bloat. Relayers help broadcast orders and
+ collect a fee each time they facilitate a trade. Anyone can build a relayer.
+ </div>
+ <div
+ className="pt3 sm-mx-auto sm-px3"
+ style={{color: CUSTOM_GRAY_TEXT, maxWidth: isSmallScreen ? 412 : 'none'}}
+ >
+ <div className="flex" style={{fontSize: 18}}>
+ <div
+ className="lg-h4 md-h4 sm-h5"
+ style={{letterSpacing: isSmallScreen ? 1 : 3, fontFamily: 'Roboto Mono'}}
+ >
+ RELAYERS BUILDING ON 0X
+ </div>
+ <div className="h5" style={{marginLeft: isSmallScreen ? 26 : 49}}>
+ <Link
+ to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_GRAY_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ view all
+ </Link>
+ </div>
+ </div>
+ <div className="lg-flex md-flex sm-clearfix pt3" style={{opacity: 0.4}}>
+ <div className="col col-4 sm-center">
+ <img
+ src="/images/landing/ethfinex.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ <div
+ className="col col-4 center"
+ >
+ <img
+ src="/images/landing/radar_relay.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ <div className="col col-4 sm-center" style={{textAlign: 'right'}}>
+ <img
+ src="/images/landing/paradex.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderBuildingBlocksSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const underlineStyle: React.CSSProperties = {
+ height: isSmallScreen ? 18 : 23,
+ lineHeight: 'none',
+ borderBottom: '2px solid #979797',
+ };
+ const descriptionStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ lineHeight: isSmallScreen ? 1.5 : 2,
+ fontWeight: 300,
+ fontSize: 15,
+ maxWidth: isSmallScreen ? 375 : 'none',
+ };
+ const callToActionStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ fontSize: 15,
+ fontWeight: 300,
+ maxWidth: isSmallScreen ? 375 : 441,
+ };
+ return (
+ <div
+ className="clearfix lg-pt4 md-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix">
+ {isSmallScreen &&
+ this.renderBlockChipImage()
+ }
+ <div
+ className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3"
+ style={{color: CUSTOM_WHITE_TEXT}}
+ >
+ <div
+ className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ A building block for dApps
+ </div>
+ <div
+ className="pb3 pt2 sm-mx-auto sm-center"
+ style={descriptionStyle}
+ >
+ 0x protocol is a pluggable building block for dApps that require exchange functionality.
+ Join the many developers that are already using 0x in their web applications and smart
+ contracts.
+ </div>
+ <div
+ className="sm-mx-auto sm-center"
+ style={callToActionStyle}
+ >
+ Learn how in our{' '}
+ <Link
+ to={WebsitePaths.ZeroExJs}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_WHITE_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ 0x.js
+ </Link>
+ {' '}and{' '}
+ <Link
+ to={WebsitePaths.SmartContracts}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_WHITE_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ smart contract
+ </Link>
+ {' '}docs
+ </div>
+ </div>
+ {!isSmallScreen &&
+ this.renderBlockChipImage()
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderBlockChipImage() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div className="col lg-col-6 md-col-6 col-12 sm-center">
+ <img
+ src="/images/landing/0x_chips.png"
+ height={isSmallScreen ? 240 : 368}
+ />
+ </div>
+ );
+ }
+ private renderTokenCloud() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div className="col lg-col-6 md-col-6 col-12 center">
+ <img
+ src="/images/landing/tokenized_world.png"
+ height={isSmallScreen ? 280 : 364.5}
+ />
+ </div>
+ );
+ }
+ private renderAssetTypes() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const assetTypes: AssetType[] = [
+ {
+ title: 'Currency',
+ imageUrl: '/images/landing/currency.png',
+ },
+ {
+ title: 'Traditional assets',
+ imageUrl: '/images/landing/stocks.png',
+ style: {paddingLeft: isSmallScreen ? 41 : 56, paddingRight: isSmallScreen ? 41 : 56},
+ },
+ {
+ title: 'Digital goods',
+ imageUrl: '/images/landing/digital_goods.png',
+ },
+ ];
+ const assets = _.map(assetTypes, (assetType: AssetType) => {
+ const style = _.isUndefined(assetType.style) ? {} : assetType.style;
+ return (
+ <div
+ key={`asset-${assetType.title}`}
+ className="center"
+ style={{opacity: 0.8, ...style}}
+ >
+ <div>
+ <img
+ src={assetType.imageUrl}
+ height="80"
+ />
+ </div>
+ <div style={{fontFamily: 'Roboto Mono', fontSize: 13.5, fontWeight: 400, opacity: 0.75}}>
+ {assetType.title}
+ </div>
+ </div>
+ );
+ });
+ return assets;
+ }
+ private renderLink(label: string, path: string, color: string, style?: React.CSSProperties) {
+ return (
+ <div
+ style={{borderBottom: `1px solid ${color}`, paddingBottom: 1, height: 20, lineHeight: 1.7, ...style}}
+ >
+ <Link
+ to={path}
+ className="text-decoration-none"
+ style={{color, fontFamily: 'Roboto Mono'}}
+ >
+ {label}
+ </Link>
+ </div>
+ );
+ }
+ private renderInfoBoxes() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const boxStyle: React.CSSProperties = {
+ maxWidth: 252,
+ height: 386,
+ backgroundColor: '#F9F9F9',
+ borderRadius: 5,
+ padding: '10px 24px 24px',
+ };
+ const boxes = _.map(boxContents, (boxContent: BoxContent) => {
+ return (
+ <div
+ key={`box-${boxContent.title}`}
+ className="col lg-col-4 md-col-4 col-12 sm-pb4"
+ >
+ <div
+ className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`}
+ style={boxStyle}
+ >
+ <div>
+ <img src={boxContent.imageUrl} style={{height: 210}} />
+ </div>
+ <div
+ className="h3"
+ style={{color: 'black', fontFamily: 'Roboto Mono'}}
+ >
+ {boxContent.title}
+ </div>
+ <div
+ className="pt2 pb2"
+ style={{fontFamily: 'Roboto Mono', fontSize: 14}}
+ >
+ {boxContent.description}
+ </div>
+ </div>
+ </div>
+ );
+
+ });
+ return (
+ <div
+ className="clearfix"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto py4 sm-mt2 clearfix"
+ style={{maxWidth: '60em'}}
+ >
+ {boxes}
+ </div>
+ </div>
+ );
+ }
+ private renderUseCases() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const isMediumScreen = this.state.screenWidth === ScreenWidths.MD;
+
+ const useCases: UseCase[] = [
+ {
+ imageUrl: '/images/landing/governance_icon.png',
+ type: 'Decentralized governance',
+ description: 'Decentralized organizations use tokens to represent ownership and \
+ guide their governance logic. 0x allows decentralized organizations \
+ to seamlessly and safely trade ownership for startup capital.',
+ projectIconUrls: ['/images/landing/aragon.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/prediction_market_icon.png',
+ type: 'Prediction markets',
+ description: 'Decentralized prediction market platforms generate sets of tokens that \
+ represent a financial stake in the outcomes of real-world events. 0x allows \
+ these tokens to be instantly tradable.',
+ projectIconUrls: ['/images/landing/augur.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/stable_tokens_icon.png',
+ type: 'Stable tokens',
+ description: 'Novel economic constructs such as stable coins require efficient, liquid \
+ markets to succeed. 0x will facilitate the underlying economic mechanisms \
+ that allow these tokens to remain stable.',
+ projectIconUrls: ['/images/landing/maker.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/loans_icon.png',
+ type: 'Decentralized loans',
+ description: 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \
+ 0x enables an ecosystem of lenders to self-organize and efficiently determine \
+ market prices for all outstanding loans.',
+ projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'],
+ classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6',
+ style: {width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0},
+ },
+ {
+ imageUrl: '/images/landing/fund_management_icon.png',
+ type: 'Fund management',
+ description: 'Decentralized fund management limits fund managers to investing in pre-agreed \
+ upon asset classes. Embedding 0x into fund management smart contracts enables \
+ them to enforce these security constraints.',
+ projectIconUrls: ['/images/landing/melonport.png'],
+ classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6',
+ style: {width: 291, marginTop: !isSmallScreen ? 38 : 0},
+ },
+ ];
+
+ const cases = _.map(useCases, (useCase: UseCase) => {
+ const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style;
+ const useCaseBoxStyle = {
+ color: '#A2A2A2',
+ border: '1px solid #565656',
+ borderRadius: 4,
+ maxWidth: isSmallScreen ? 375 : 'none',
+ ...style,
+ };
+ const typeStyle: React.CSSProperties = {
+ color: '#EBEBEB',
+ fontSize: 13,
+ textTransform: 'uppercase',
+ fontFamily: 'Roboto Mono',
+ fontWeight: 300,
+ };
+ return (
+ <div
+ key={`useCase-${useCase.type}`}
+ className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`}
+ >
+ <div
+ className="relative p2 pb2 sm-mx-auto"
+ style={useCaseBoxStyle}
+ >
+ <div
+ className="absolute center"
+ style={{top: -35, width: 'calc(100% - 32px)'}}
+ >
+ <img src={useCase.imageUrl} style={{height: 50}} />
+ </div>
+ <div className="pt2 center" style={typeStyle}>
+ {useCase.type}
+ </div>
+ <div
+ className="pt2"
+ style={{lineHeight: 1.5, fontSize: 14, overflow: 'hidden', height: 104}}
+ >
+ {useCase.description}
+ </div>
+ </div>
+ </div>
+ );
+ });
+ return (
+ <div
+ className="clearfix pb4 lg-pt2 md-pt2 sm-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix"
+ style={{maxWidth: '67em'}}
+ >
+ {cases}
+ </div>
+ </div>
+ );
+ }
+ private renderCallToAction() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const buttonLabelStyle: React.CSSProperties = {
+ textTransform: 'none',
+ fontSize: 15,
+ fontWeight: 400,
+ };
+ const lightButtonStyle: React.CSSProperties = {
+ borderRadius: 6,
+ border: '1px solid #a0a0a0',
+ lineHeight: '33px',
+ height: 49,
+ };
+ const callToActionClassNames = 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \
+ lg-right-align md-right-align sm-center sm-px3 h4';
+ return (
+ <div
+ className="clearfix pb4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto max-width-4 pb4 mb3 clearfix"
+ >
+ <div
+ className={callToActionClassNames}
+ style={{fontFamily: 'Roboto Mono', color: 'white', lineHeight: isSmallScreen ? 1.7 : 3}}
+ >
+ Get started on building the decentralized future
+ </div>
+ <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2">
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 150}}
+ buttonStyle={lightButtonStyle}
+ labelColor={colors.white}
+ backgroundColor={CUSTOM_HERO_BACKGROUND_COLOR}
+ labelStyle={buttonLabelStyle}
+ label="Build on 0x"
+ onClick={_.noop}
+ />
+ </Link>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private updateScreenWidth() {
+ const newScreenWidth = utils.getScreenWidth();
+ if (newScreenWidth !== this.state.screenWidth) {
+ this.setState({
+ screenWidth: newScreenWidth,
+ });
+ }
+ }
+}
diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx
new file mode 100644
index 000000000..ddd720c97
--- /dev/null
+++ b/packages/website/ts/pages/not_found.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Styles} from 'ts/types';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+
+export interface NotFoundProps {
+ location: Location;
+}
+
+interface NotFoundState {}
+
+const styles: Styles = {
+ thin: {
+ fontWeight: 100,
+ },
+};
+
+export class NotFound extends React.Component<NotFoundProps, NotFoundState> {
+ public render() {
+ return (
+ <div>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ />
+ <div className="mx-auto max-width-4 py4">
+ <div className="center py4">
+ <div className="py4">
+ <div className="py4">
+ <h1 style={{...styles.thin}}>404 Not Found</h1>
+ <div className="py1">
+ <div className="py3">
+ Hm... looks like we couldn't find what you are looking for.
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx
new file mode 100644
index 000000000..dfa9401ae
--- /dev/null
+++ b/packages/website/ts/pages/shared/anchor_title.tsx
@@ -0,0 +1,98 @@
+import * as React from 'react';
+import {Styles, HeaderSizes} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {Link as ScrollLink} from 'react-scroll';
+
+const headerSizeToScrollOffset: {[headerSize: string]: number} = {
+ h2: -20,
+ h3: 0,
+};
+
+interface AnchorTitleProps {
+ title: string|React.ReactNode;
+ id: string;
+ headerSize: HeaderSizes;
+ shouldShowAnchor: boolean;
+}
+
+interface AnchorTitleState {
+ isHovering: boolean;
+}
+
+const styles: Styles = {
+ anchor: {
+ fontSize: 20,
+ transform: 'rotate(45deg)',
+ cursor: 'pointer',
+ },
+ headers: {
+ WebkitMarginStart: 0,
+ WebkitMarginEnd: 0,
+ fontWeight: 'bold',
+ display: 'block',
+ },
+ h1: {
+ fontSize: '1.8em',
+ WebkitMarginBefore: '0.83em',
+ WebkitMarginAfter: '0.83em',
+ },
+ h2: {
+ fontSize: '1.5em',
+ WebkitMarginBefore: '0.83em',
+ WebkitMarginAfter: '0.83em',
+ },
+ h3: {
+ fontSize: '1.17em',
+ WebkitMarginBefore: '1em',
+ WebkitMarginAfter: '1em',
+ },
+};
+
+export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> {
+ constructor(props: AnchorTitleProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ let opacity = 0;
+ if (this.props.shouldShowAnchor) {
+ if (this.state.isHovering) {
+ opacity = 0.6;
+ } else {
+ opacity = 1;
+ }
+ }
+ return (
+ <div className="relative flex" style={{...styles[this.props.headerSize], ...styles.headers}}>
+ <div
+ className="inline-block"
+ style={{paddingRight: 4}}
+ >
+ {this.props.title}
+ </div>
+ <ScrollLink
+ to={this.props.id}
+ offset={headerSizeToScrollOffset[this.props.headerSize]}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <i
+ className="zmdi zmdi-link"
+ onClick={utils.setUrlHash.bind(utils, this.props.id)}
+ style={{...styles.anchor, opacity}}
+ onMouseOver={this.setHoverState.bind(this, true)}
+ onMouseOut={this.setHoverState.bind(this, false)}
+ />
+ </ScrollLink>
+ </div>
+ );
+ }
+ private setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx
new file mode 100644
index 000000000..621e5b606
--- /dev/null
+++ b/packages/website/ts/pages/shared/markdown_code_block.tsx
@@ -0,0 +1,20 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as HighLight from 'react-highlight';
+
+interface MarkdownCodeBlockProps {
+ literal: string;
+ language: string;
+}
+
+export function MarkdownCodeBlock(props: MarkdownCodeBlockProps) {
+ return (
+ <span style={{fontSize: 16}}>
+ <HighLight
+ className={props.language || 'js'}
+ >
+ {props.literal}
+ </HighLight>
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx
new file mode 100644
index 000000000..32b55abc8
--- /dev/null
+++ b/packages/website/ts/pages/shared/markdown_section.tsx
@@ -0,0 +1,77 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {Element as ScrollElement} from 'react-scroll';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {utils} from 'ts/utils/utils';
+import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block';
+import RaisedButton from 'material-ui/RaisedButton';
+import {HeaderSizes} from 'ts/types';
+
+interface MarkdownSectionProps {
+ sectionName: string;
+ markdownContent: string;
+ headerSize?: HeaderSizes;
+ githubLink?: string;
+}
+
+interface MarkdownSectionState {
+ shouldShowAnchor: boolean;
+}
+
+export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> {
+ public static defaultProps: Partial<MarkdownSectionProps> = {
+ headerSize: HeaderSizes.H3,
+ };
+ constructor(props: MarkdownSectionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const sectionName = this.props.sectionName;
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden"
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <div className="clearfix">
+ <div className="col lg-col-8 md-col-8 sm-col-12">
+ <span style={{textTransform: 'capitalize'}}>
+ <AnchorTitle
+ headerSize={this.props.headerSize}
+ title={sectionName}
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </span>
+ </div>
+ <div className="col col-4 sm-hide xs-hide py2 right-align">
+ {!_.isUndefined(this.props.githubLink) &&
+ <RaisedButton
+ href={this.props.githubLink}
+ target="_blank"
+ label="Edit on Github"
+ icon={<i className="zmdi zmdi-github" style={{fontSize: 23}} />}
+ />
+ }
+ </div>
+ </div>
+ <ReactMarkdown
+ source={this.props.markdownContent}
+ renderers={{CodeBlock: MarkdownCodeBlock}}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx
new file mode 100644
index 000000000..e69506bb8
--- /dev/null
+++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx
@@ -0,0 +1,163 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import MenuItem from 'material-ui/MenuItem';
+import {colors} from 'material-ui/styles';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {VersionDropDown} from 'ts/pages/shared/version_drop_down';
+import {ZeroExJsDocSections, Styles, MenuSubsectionsBySection, Docs} from 'ts/types';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+import {Link as ScrollLink} from 'react-scroll';
+
+interface NestedSidebarMenuProps {
+ topLevelMenu: {[topLevel: string]: string[]};
+ menuSubsectionsBySection: MenuSubsectionsBySection;
+ shouldDisplaySectionHeaders?: boolean;
+ onMenuItemClick?: () => void;
+ selectedVersion?: string;
+ versions?: string[];
+ doc?: Docs;
+ isSectionHeaderClickable?: boolean;
+}
+
+interface NestedSidebarMenuState {}
+
+const styles: Styles = {
+ menuItemWithHeaders: {
+ minHeight: 0,
+ },
+ menuItemWithoutHeaders: {
+ minHeight: 48,
+ },
+ menuItemInnerDivWithHeaders: {
+ lineHeight: 2,
+ },
+};
+
+export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> {
+ public static defaultProps: Partial<NestedSidebarMenuProps> = {
+ shouldDisplaySectionHeaders: true,
+ onMenuItemClick: _.noop,
+ };
+ public render() {
+ const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => {
+ const finalSectionName = sectionName.replace(/-/g, ' ');
+ if (this.props.shouldDisplaySectionHeaders) {
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py1"
+ >
+ <ScrollLink
+ to={id}
+ offset={-20}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <div
+ style={{color: colors.grey500, cursor: 'pointer'}}
+ className="pb1"
+ >
+ {finalSectionName.toUpperCase()}
+ </div>
+ </ScrollLink>
+ {this.renderMenuItems(menuItems)}
+ </div>
+ );
+ } else {
+ return (
+ <div key={`section-${sectionName}`} >
+ {this.renderMenuItems(menuItems)}
+ </div>
+ );
+ }
+ });
+ return (
+ <div>
+ {!_.isUndefined(this.props.versions) &&
+ !_.isUndefined(this.props.selectedVersion) &&
+ !_.isUndefined(this.props.doc) &&
+ <VersionDropDown
+ selectedVersion={this.props.selectedVersion}
+ versions={this.props.versions}
+ doc={this.props.doc}
+ />
+ }
+ {navigation}
+ </div>
+ );
+ }
+ private renderMenuItems(menuItemNames: string[]): React.ReactNode[] {
+ const menuItemStyles = this.props.shouldDisplaySectionHeaders ?
+ styles.menuItemWithHeaders :
+ styles.menuItemWithoutHeaders;
+ const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ?
+ styles.menuItemInnerDivWithHeaders : {};
+ const menuItems = _.map(menuItemNames, menuItemName => {
+ const id = utils.getIdFromName(menuItemName);
+ return (
+ <div key={menuItemName}>
+ <ScrollLink
+ key={`menuItem-${menuItemName}`}
+ to={id}
+ offset={-10}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <MenuItem
+ onTouchTap={this.onMenuItemClick.bind(this, menuItemName)}
+ style={menuItemStyles}
+ innerDivStyle={menuItemInnerDivStyles}
+ >
+ <span style={{textTransform: 'capitalize'}}>
+ {menuItemName}
+ </span>
+ </MenuItem>
+ </ScrollLink>
+ {this.renderMenuItemSubsections(menuItemName)}
+ </div>
+ );
+ });
+ return menuItems;
+ }
+ private renderMenuItemSubsections(menuItemName: string): React.ReactNode {
+ if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) {
+ return null;
+ }
+ return this.renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]);
+ }
+ private renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode {
+ return (
+ <ul style={{margin: 0, listStyleType: 'none', paddingLeft: 0}} key={menuItemName}>
+ {_.map(entityNames, entityName => {
+ const id = utils.getIdFromName(entityName);
+ return (
+ <li key={`menuItem-${entityName}`}>
+ <ScrollLink
+ to={id}
+ offset={0}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ onTouchTap={this.onMenuItemClick.bind(this, entityName)}
+ >
+ <MenuItem
+ onTouchTap={this.onMenuItemClick.bind(this, menuItemName)}
+ style={{minHeight: 35}}
+ innerDivStyle={{paddingLeft: 36, fontSize: 14, lineHeight: '35px'}}
+ >
+ {entityName}
+ </MenuItem>
+ </ScrollLink>
+ </li>
+ );
+ })}
+ </ul>
+ );
+ }
+ private onMenuItemClick(menuItemName: string): void {
+ const id = utils.getIdFromName(menuItemName);
+ utils.setUrlHash(id);
+ this.props.onMenuItemClick();
+ }
+}
diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx
new file mode 100644
index 000000000..5937be13b
--- /dev/null
+++ b/packages/website/ts/pages/shared/section_header.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import {Element as ScrollElement} from 'react-scroll';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {utils} from 'ts/utils/utils';
+import {HeaderSizes} from 'ts/types';
+
+interface SectionHeaderProps {
+ sectionName: string;
+ headerSize?: HeaderSizes;
+}
+
+interface SectionHeaderState {
+ shouldShowAnchor: boolean;
+}
+
+export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> {
+ public static defaultProps: Partial<SectionHeaderProps> = {
+ headerSize: HeaderSizes.H2,
+ };
+ constructor(props: SectionHeaderProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const sectionName = this.props.sectionName.replace(/-/g, ' ');
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <AnchorTitle
+ headerSize={this.props.headerSize}
+ title={<span style={{textTransform: 'capitalize'}}>{sectionName}</span>}
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx
new file mode 100644
index 000000000..f29547c9c
--- /dev/null
+++ b/packages/website/ts/pages/shared/version_drop_down.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import MenuItem from 'material-ui/MenuItem';
+import DropDownMenu from 'material-ui/DropDownMenu';
+import {constants} from 'ts/utils/constants';
+import {Docs} from 'ts/types';
+
+interface VersionDropDownProps {
+ selectedVersion: string;
+ versions: string[];
+ doc: Docs;
+}
+
+interface VersionDropDownState {}
+
+export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> {
+ public render() {
+ return (
+ <div className="mx-auto" style={{width: 120}}>
+ <DropDownMenu
+ maxHeight={300}
+ value={this.props.selectedVersion}
+ onChange={this.updateSelectedVersion.bind(this)}
+ >
+ {this.renderDropDownItems()}
+ </DropDownMenu>
+ </div>
+ );
+ }
+ private renderDropDownItems() {
+ const items = _.map(this.props.versions, version => {
+ return (
+ <MenuItem
+ key={version}
+ value={version}
+ primaryText={`v${version}`}
+ />
+ );
+ });
+ return items;
+ }
+ private updateSelectedVersion(e: any, index: number, value: string) {
+ const docPath = constants.docToPath[this.props.doc];
+ window.location.href = `${docPath}/${value}${window.location.hash}`;
+ }
+}
diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx
new file mode 100644
index 000000000..0e6fc98ab
--- /dev/null
+++ b/packages/website/ts/pages/wiki/wiki.tsx
@@ -0,0 +1,210 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import {colors} from 'material-ui/styles';
+import CircularProgress from 'material-ui/CircularProgress';
+import {
+ scroller,
+} from 'react-scroll';
+import {Styles, Article, ArticlesBySection} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {HeaderSizes, WebsitePaths} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+
+const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000;
+
+export interface WikiProps {
+ source: string;
+ location: Location;
+}
+
+interface WikiState {
+ articlesBySection: ArticlesBySection;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class Wiki extends React.Component<WikiProps, WikiState> {
+ private wikiBackoffTimeoutId: number;
+ constructor(props: WikiProps) {
+ super(props);
+ this.state = {
+ articlesBySection: undefined,
+ };
+ }
+ public componentWillMount() {
+ this.fetchArticlesBySectionAsync();
+ }
+ public componentWillUnmount() {
+ clearTimeout(this.wikiBackoffTimeoutId);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection)
+ ? {}
+ : this.getMenuSubsectionsBySection(this.state.articlesBySection);
+ return (
+ <div>
+ <DocumentTitle title="0x Protocol Wiki"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ />
+ {_.isUndefined(this.state.articlesBySection) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading wiki...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute pt2"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ topLevelMenu={menuSubsectionsBySection}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ isSectionHeaderClickable={true}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="0xProtocolWiki" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_WIKI_URL} target="_blank">
+ 0x Protocol Wiki
+ </a>
+ </h1>
+ <div id="wiki">
+ {this.renderWikiArticles()}
+ </div>
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderWikiArticles(): React.ReactNode {
+ const sectionNames = _.keys(this.state.articlesBySection);
+ const sections = _.map(sectionNames, sectionName => this.renderSection(sectionName));
+ return sections;
+ }
+ private renderSection(sectionName: string) {
+ const articles = this.state.articlesBySection[sectionName];
+ const renderedArticles = _.map(articles, (article: Article) => {
+ const githubLink = `${constants.GITHUB_WIKI_URL}/edit/master/${sectionName}/${article.fileName}`;
+ return (
+ <div key={`markdown-section-${article.title}`}>
+ <MarkdownSection
+ sectionName={article.title}
+ markdownContent={article.content}
+ headerSize={HeaderSizes.H2}
+ githubLink={githubLink}
+ />
+ <div className="mb4 mt3 p3 center" style={{backgroundColor: '#f9f5ef'}}>
+ See a way to make this article better?{' '}
+ <a
+ href={githubLink}
+ target="_blank"
+ >
+ Edit here →
+ </a>
+ </div>
+ </div>
+ );
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} />
+ {renderedArticles}
+ </div>
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = '0xProtocolWiki'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private async fetchArticlesBySectionAsync(): Promise<void> {
+ const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`;
+ const response = await fetch(endpoint);
+ if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) {
+ // We need to backoff and try fetching again later
+ this.wikiBackoffTimeoutId = window.setTimeout(() => {
+ this.fetchArticlesBySectionAsync();
+ }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS);
+ return;
+ }
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the wiki fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`);
+ return;
+ }
+ const articlesBySection = await response.json();
+ this.setState({
+ articlesBySection,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+ private getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) {
+ const sectionNames = _.keys(articlesBySection);
+ const menuSubsectionsBySection: {[section: string]: string[]} = {};
+ for (const sectionName of sectionNames) {
+ const articles = articlesBySection[sectionName];
+ const articleNames = _.map(articles, article => article.title);
+ menuSubsectionsBySection[sectionName] = articleNames;
+ }
+ return menuSubsectionsBySection;
+ }
+}
diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts
new file mode 100644
index 000000000..6badf95bd
--- /dev/null
+++ b/packages/website/ts/redux/dispatcher.ts
@@ -0,0 +1,244 @@
+import {Dispatch} from 'redux';
+import {State} from 'ts/redux/reducer';
+import {
+ Direction,
+ Side,
+ AssetToken,
+ BlockchainErrs,
+ Token,
+ SignatureData,
+ Fill,
+ Order,
+ ActionTypes,
+ ScreenWidths,
+ ProviderType,
+ TokenStateByAddress,
+} from 'ts/types';
+import BigNumber from 'bignumber.js';
+
+export class Dispatcher {
+ private dispatch: Dispatch<State>;
+ constructor(dispatch: Dispatch<State>) {
+ this.dispatch = dispatch;
+ }
+ // Portal
+ public resetState() {
+ this.dispatch({
+ type: ActionTypes.RESET_STATE,
+ });
+ }
+ public updateNodeVersion(nodeVersion: string) {
+ this.dispatch({
+ data: nodeVersion,
+ type: ActionTypes.UPDATE_NODE_VERSION,
+ });
+ }
+ public updateScreenWidth(screenWidth: ScreenWidths) {
+ this.dispatch({
+ data: screenWidth,
+ type: ActionTypes.UPDATE_SCREEN_WIDTH,
+ });
+ }
+ public swapAssetTokenSymbols() {
+ this.dispatch({
+ type: ActionTypes.SWAP_ASSET_TOKENS,
+ });
+ }
+ public updateGenerateOrderStep(direction: Direction) {
+ this.dispatch({
+ data: direction,
+ type: ActionTypes.UPDATE_GENERATE_ORDER_STEP,
+ });
+ }
+ public updateOrderSalt(salt: BigNumber) {
+ this.dispatch({
+ data: salt,
+ type: ActionTypes.UPDATE_ORDER_SALT,
+ });
+ }
+ public updateUserSuppliedOrderCache(order: Order) {
+ this.dispatch({
+ data: order,
+ type: ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE,
+ });
+ }
+ public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) {
+ this.dispatch({
+ data: shouldBeOpen,
+ type: ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN,
+ });
+ }
+ public updateChosenAssetToken(side: Side, token: AssetToken) {
+ this.dispatch({
+ data: {
+ side,
+ token,
+ },
+ type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN,
+ });
+ }
+ public updateChosenAssetTokenAddress(side: Side, address: string) {
+ this.dispatch({
+ data: {
+ address,
+ side,
+ },
+ type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS,
+ });
+ }
+ public updateOrderTakerAddress(address: string) {
+ this.dispatch({
+ data: address,
+ type: ActionTypes.UPDATE_ORDER_TAKER_ADDRESS,
+ });
+ }
+ public updateUserAddress(address: string) {
+ this.dispatch({
+ data: address,
+ type: ActionTypes.UPDATE_USER_ADDRESS,
+ });
+ }
+ public updateOrderExpiry(unixTimestampSec: BigNumber) {
+ this.dispatch({
+ data: unixTimestampSec,
+ type: ActionTypes.UPDATE_ORDER_EXPIRY,
+ });
+ }
+ public encounteredBlockchainError(err: BlockchainErrs) {
+ this.dispatch({
+ data: err,
+ type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED,
+ });
+ }
+ public updateBlockchainIsLoaded(isLoaded: boolean) {
+ this.dispatch({
+ data: isLoaded,
+ type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED,
+ });
+ }
+ public addTokenToTokenByAddress(token: Token) {
+ this.dispatch({
+ data: token,
+ type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS,
+ });
+ }
+ public removeTokenToTokenByAddress(token: Token) {
+ this.dispatch({
+ data: token,
+ type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS,
+ });
+ }
+ public clearTokenByAddress() {
+ this.dispatch({
+ type: ActionTypes.CLEAR_TOKEN_BY_ADDRESS,
+ });
+ }
+ public updateTokenByAddress(tokens: Token[]) {
+ this.dispatch({
+ data: tokens,
+ type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS,
+ });
+ }
+ public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) {
+ this.dispatch({
+ data: tokenStateByAddress,
+ type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS,
+ });
+ }
+ public removeFromTokenStateByAddress(tokenAddress: string) {
+ this.dispatch({
+ data: tokenAddress,
+ type: ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS,
+ });
+ }
+ public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) {
+ this.dispatch({
+ data: {
+ address,
+ allowance,
+ },
+ type: ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS,
+ });
+ }
+ public replaceTokenBalanceByAddress(address: string, balance: BigNumber) {
+ this.dispatch({
+ data: {
+ address,
+ balance,
+ },
+ type: ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS,
+ });
+ }
+ public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) {
+ this.dispatch({
+ data: {
+ address,
+ balanceDelta,
+ },
+ type: ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS,
+ });
+ }
+ public updateSignatureData(signatureData: SignatureData) {
+ this.dispatch({
+ data: signatureData,
+ type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA,
+ });
+ }
+ public updateUserEtherBalance(balance: BigNumber) {
+ this.dispatch({
+ data: balance,
+ type: ActionTypes.UPDATE_USER_ETHER_BALANCE,
+ });
+ }
+ public updateNetworkId(networkId: number) {
+ this.dispatch({
+ data: networkId,
+ type: ActionTypes.UPDATE_NETWORK_ID,
+ });
+ }
+ public updateOrderFillAmount(amount: BigNumber) {
+ this.dispatch({
+ data: amount,
+ type: ActionTypes.UPDATE_ORDER_FILL_AMOUNT,
+ });
+ }
+
+ // Docs
+ public updateCurrentDocsVersion(version: string) {
+ this.dispatch({
+ data: version,
+ type: ActionTypes.UPDATE_LIBRARY_VERSION,
+ });
+ }
+ public updateAvailableDocVersions(versions: string[]) {
+ this.dispatch({
+ data: versions,
+ type: ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS,
+ });
+ }
+
+ // Shared
+ public showFlashMessage(msg: string|React.ReactNode) {
+ this.dispatch({
+ data: msg,
+ type: ActionTypes.SHOW_FLASH_MESSAGE,
+ });
+ }
+ public hideFlashMessage() {
+ this.dispatch({
+ type: ActionTypes.HIDE_FLASH_MESSAGE,
+ });
+ }
+ public updateProviderType(providerType: ProviderType) {
+ this.dispatch({
+ type: ActionTypes.UPDATE_PROVIDER_TYPE,
+ data: providerType,
+ });
+ }
+ public updateInjectedProviderName(injectedProviderName: string) {
+ this.dispatch({
+ type: ActionTypes.UPDATE_INJECTED_PROVIDER_NAME,
+ data: injectedProviderName,
+ });
+ }
+}
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
new file mode 100644
index 000000000..7723597cd
--- /dev/null
+++ b/packages/website/ts/redux/reducer.ts
@@ -0,0 +1,363 @@
+import * as _ from 'lodash';
+import {ZeroEx} from '0x.js';
+import BigNumber from 'bignumber.js';
+import {utils} from 'ts/utils/utils';
+import {
+ GenerateOrderSteps,
+ Side,
+ SideToAssetToken,
+ Direction,
+ BlockchainErrs,
+ SignatureData,
+ TokenByAddress,
+ TokenStateByAddress,
+ Order,
+ Action,
+ ActionTypes,
+ ScreenWidths,
+ ProviderType,
+ TokenState,
+} from 'ts/types';
+
+// Instead of defaulting the docs version to an empty string, we pre-populate it with
+// a valid version value. This does not need to be updated however, since onLoad, it
+// is always replaced with a value retrieved from our S3 bucket.
+const DEFAULT_DOCS_VERSION = '0.0.0';
+
+export interface State {
+ // Portal
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ generateOrderStep: GenerateOrderSteps;
+ networkId: number;
+ orderExpiryTimestamp: BigNumber;
+ orderFillAmount: BigNumber;
+ orderTakerAddress: string;
+ orderSignatureData: SignatureData;
+ orderSalt: BigNumber;
+ nodeVersion: string;
+ screenWidth: ScreenWidths;
+ shouldBlockchainErrDialogBeOpen: boolean;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userAddress: string;
+ userEtherBalance: BigNumber;
+ // Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
+ userSuppliedOrderCache: Order;
+
+ // Docs
+ docsVersion: string;
+ availableDocVersions: string[];
+
+ // Shared
+ flashMessage: string|React.ReactNode;
+ providerType: ProviderType;
+ injectedProviderName: string;
+};
+
+const INITIAL_STATE: State = {
+ // Portal
+ blockchainErr: '',
+ blockchainIsLoaded: false,
+ generateOrderStep: GenerateOrderSteps.ChooseAssets,
+ networkId: undefined,
+ orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(),
+ orderFillAmount: undefined,
+ orderSignatureData: {
+ hash: '',
+ r: '',
+ s: '',
+ v: 27,
+ },
+ orderTakerAddress: '',
+ orderSalt: ZeroEx.generatePseudoRandomSalt(),
+ nodeVersion: undefined,
+ screenWidth: utils.getScreenWidth(),
+ shouldBlockchainErrDialogBeOpen: false,
+ sideToAssetToken: {
+ [Side.deposit]: {},
+ [Side.receive]: {},
+ },
+ tokenByAddress: {},
+ tokenStateByAddress: {},
+ userAddress: '',
+ userEtherBalance: new BigNumber(0),
+ userSuppliedOrderCache: undefined,
+
+ // Docs
+ docsVersion: DEFAULT_DOCS_VERSION,
+ availableDocVersions: [DEFAULT_DOCS_VERSION],
+
+ // Shared
+ flashMessage: undefined,
+ providerType: ProviderType.INJECTED,
+ injectedProviderName: '',
+};
+
+export function reducer(state: State = INITIAL_STATE, action: Action) {
+ switch (action.type) {
+ // Portal
+ case ActionTypes.RESET_STATE:
+ return INITIAL_STATE;
+
+ case ActionTypes.UPDATE_ORDER_SALT: {
+ return _.assign({}, state, {
+ orderSalt: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_NODE_VERSION: {
+ return _.assign({}, state, {
+ nodeVersion: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_ORDER_FILL_AMOUNT: {
+ return _.assign({}, state, {
+ orderFillAmount: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN: {
+ return _.assign({}, state, {
+ shouldBlockchainErrDialogBeOpen: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_USER_ETHER_BALANCE: {
+ return _.assign({}, state, {
+ userEtherBalance: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE: {
+ return _.assign({}, state, {
+ userSuppliedOrderCache: action.data,
+ });
+ }
+
+ case ActionTypes.CLEAR_TOKEN_BY_ADDRESS: {
+ return _.assign({}, state, {
+ tokenByAddress: {},
+ });
+ }
+
+ case ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS: {
+ const newTokenByAddress = state.tokenByAddress;
+ newTokenByAddress[action.data.address] = action.data;
+ return _.assign({}, state, {
+ tokenByAddress: newTokenByAddress,
+ });
+ }
+
+ case ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS: {
+ const newTokenByAddress = state.tokenByAddress;
+ delete newTokenByAddress[action.data.address];
+ return _.assign({}, state, {
+ tokenByAddress: newTokenByAddress,
+ });
+ }
+
+ case ActionTypes.UPDATE_TOKEN_BY_ADDRESS: {
+ const tokenByAddress = state.tokenByAddress;
+ const tokens = action.data;
+ _.each(tokens, token => {
+ const updatedToken = _.assign({}, tokenByAddress[token.address], token);
+ tokenByAddress[token.address] = updatedToken;
+ });
+ return _.assign({}, state, {
+ tokenByAddress,
+ });
+ }
+
+ case ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS: {
+ const tokenStateByAddress = state.tokenStateByAddress;
+ const updatedTokenStateByAddress = action.data;
+ _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => {
+ const updatedTokenState = _.assign({}, tokenStateByAddress[address], tokenState);
+ tokenStateByAddress[address] = updatedTokenState;
+ });
+ return _.assign({}, state, {
+ tokenStateByAddress,
+ });
+ }
+
+ case ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS: {
+ const tokenStateByAddress = state.tokenStateByAddress;
+ const tokenAddress = action.data;
+ delete tokenStateByAddress[tokenAddress];
+ return _.assign({}, state, {
+ tokenStateByAddress,
+ });
+ }
+
+ case ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS: {
+ const tokenStateByAddress = state.tokenStateByAddress;
+ const allowance = action.data.allowance;
+ const tokenAddress = action.data.address;
+ tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
+ allowance,
+ });
+ return _.assign({}, state, {
+ tokenStateByAddress,
+ });
+ }
+
+ case ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS: {
+ const tokenStateByAddress = state.tokenStateByAddress;
+ const balance = action.data.balance;
+ const tokenAddress = action.data.address;
+ tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
+ balance,
+ });
+ return _.assign({}, state, {
+ tokenStateByAddress,
+ });
+ }
+
+ case ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS: {
+ const tokenStateByAddress = state.tokenStateByAddress;
+ const balanceDelta = action.data.balanceDelta;
+ const tokenAddress = action.data.address;
+ const currBalance = tokenStateByAddress[tokenAddress].balance;
+ tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
+ balance: currBalance.plus(balanceDelta),
+ });
+ return _.assign({}, state, {
+ tokenStateByAddress,
+ });
+ }
+
+ case ActionTypes.UPDATE_ORDER_SIGNATURE_DATA: {
+ return _.assign({}, state, {
+ orderSignatureData: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_SCREEN_WIDTH: {
+ return _.assign({}, state, {
+ screenWidth: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED: {
+ return _.assign({}, state, {
+ blockchainIsLoaded: action.data,
+ });
+ }
+
+ case ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED: {
+ return _.assign({}, state, {
+ blockchainErr: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_NETWORK_ID: {
+ return _.assign({}, state, {
+ networkId: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_GENERATE_ORDER_STEP: {
+ const direction = action.data;
+ let nextGenerateOrderStep = state.generateOrderStep;
+ if (direction === Direction.forward) {
+ nextGenerateOrderStep += 1;
+ } else if (state.generateOrderStep !== 0) {
+ nextGenerateOrderStep -= 1;
+ }
+ return _.assign({}, state, {
+ generateOrderStep: nextGenerateOrderStep,
+ });
+ }
+
+ case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN: {
+ const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
+ [action.data.side]: action.data.token,
+ });
+ return _.assign({}, state, {
+ sideToAssetToken: newSideToAssetToken,
+ });
+ }
+
+ case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS: {
+ const newAssetToken = state.sideToAssetToken[action.data.side];
+ newAssetToken.address = action.data.address;
+ const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
+ [action.data.side]: newAssetToken,
+ });
+ return _.assign({}, state, {
+ sideToAssetToken: newSideToAssetToken,
+ });
+ }
+
+ case ActionTypes.SWAP_ASSET_TOKENS: {
+ const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
+ [Side.deposit]: state.sideToAssetToken[Side.receive],
+ [Side.receive]: state.sideToAssetToken[Side.deposit],
+ });
+ return _.assign({}, state, {
+ sideToAssetToken: newSideToAssetToken,
+ });
+ }
+
+ case ActionTypes.UPDATE_ORDER_EXPIRY: {
+ return _.assign({}, state, {
+ orderExpiryTimestamp: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_ORDER_TAKER_ADDRESS: {
+ return _.assign({}, state, {
+ orderTakerAddress: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_USER_ADDRESS: {
+ return _.assign({}, state, {
+ userAddress: action.data,
+ });
+ }
+
+ // Docs
+ case ActionTypes.UPDATE_LIBRARY_VERSION: {
+ return _.assign({}, state, {
+ docsVersion: action.data,
+ });
+ }
+ case ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS: {
+ return _.assign({}, state, {
+ availableDocVersions: action.data,
+ });
+ }
+
+ // Shared
+ case ActionTypes.SHOW_FLASH_MESSAGE: {
+ return _.assign({}, state, {
+ flashMessage: action.data,
+ });
+ }
+
+ case ActionTypes.HIDE_FLASH_MESSAGE: {
+ return _.assign({}, state, {
+ flashMessage: undefined,
+ });
+ }
+
+ case ActionTypes.UPDATE_PROVIDER_TYPE: {
+ return _.assign({}, state, {
+ providerType: action.data,
+ });
+ }
+
+ case ActionTypes.UPDATE_INJECTED_PROVIDER_NAME: {
+ return _.assign({}, state, {
+ injectedProviderName: action.data,
+ });
+ }
+
+ default:
+ return state;
+ }
+}
diff --git a/packages/website/ts/schemas/order_schema.ts b/packages/website/ts/schemas/order_schema.ts
new file mode 100644
index 000000000..61b93d273
--- /dev/null
+++ b/packages/website/ts/schemas/order_schema.ts
@@ -0,0 +1,24 @@
+export const orderSchema = {
+ id: '/Order',
+ properties: {
+ maker: {$ref: '/OrderTaker'},
+ taker: {$ref: '/OrderTaker'},
+ salt: {type: 'string'},
+ signature: {$ref: '/SignatureData'},
+ expiration: {type: 'string'},
+ feeRecipient: {type: 'string'},
+ exchangeContract: {type: 'string'},
+ networkId: {type: 'number'},
+ },
+ required: [
+ 'maker',
+ 'taker',
+ 'salt',
+ 'signature',
+ 'expiration',
+ 'feeRecipient',
+ 'exchangeContract',
+ 'networkId',
+ ],
+ type: 'object',
+};
diff --git a/packages/website/ts/schemas/order_taker_schema.ts b/packages/website/ts/schemas/order_taker_schema.ts
new file mode 100644
index 000000000..6b484a60d
--- /dev/null
+++ b/packages/website/ts/schemas/order_taker_schema.ts
@@ -0,0 +1,11 @@
+export const orderTakerSchema = {
+ id: '/OrderTaker',
+ properties: {
+ address: {type: 'string'},
+ token: {$ref: '/Token'},
+ amount: {type: 'string'},
+ feeAmount: {type: 'string'},
+ },
+ required: ['address', 'token', 'amount', 'feeAmount'],
+ type: 'object',
+};
diff --git a/packages/website/ts/schemas/signature_data_schema.ts b/packages/website/ts/schemas/signature_data_schema.ts
new file mode 100644
index 000000000..d208cc438
--- /dev/null
+++ b/packages/website/ts/schemas/signature_data_schema.ts
@@ -0,0 +1,11 @@
+export const signatureDataSchema = {
+ id: '/SignatureData',
+ properties: {
+ hash: {type: 'string'},
+ r: {type: 'string'},
+ s: {type: 'string'},
+ v: {type: 'number'},
+ },
+ required: ['hash', 'r', 's', 'v'],
+ type: 'object',
+};
diff --git a/packages/website/ts/schemas/token_schema.ts b/packages/website/ts/schemas/token_schema.ts
new file mode 100644
index 000000000..c15f57429
--- /dev/null
+++ b/packages/website/ts/schemas/token_schema.ts
@@ -0,0 +1,11 @@
+export const tokenSchema = {
+ id: '/Token',
+ properties: {
+ name: {type: 'string'},
+ symbol: {type: 'string'},
+ decimals: {type: 'number'},
+ address: {type: 'string'},
+ },
+ required: ['name', 'symbol', 'decimals', 'address'],
+ type: 'object',
+};
diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts
new file mode 100644
index 000000000..bf6ba4044
--- /dev/null
+++ b/packages/website/ts/schemas/validator.ts
@@ -0,0 +1,19 @@
+import {Validator, Schema as JSONSchema} from 'jsonschema';
+import {signatureDataSchema} from 'ts/schemas/signature_data_schema';
+import {orderSchema} from 'ts/schemas/order_schema';
+import {tokenSchema} from 'ts/schemas/token_schema';
+import {orderTakerSchema} from 'ts/schemas/order_taker_schema';
+
+export class SchemaValidator {
+ private validator: Validator;
+ constructor() {
+ this.validator = new Validator();
+ this.validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id);
+ this.validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id);
+ this.validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id);
+ this.validator.addSchema(orderSchema as JSONSchema, orderSchema.id);
+ }
+ public validate(instance: object, schema: Schema) {
+ return this.validator.validate(instance, schema);
+ }
+}
diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/website/ts/subproviders/injected_web3_subprovider.ts
new file mode 100644
index 000000000..b9e5af3ef
--- /dev/null
+++ b/packages/website/ts/subproviders/injected_web3_subprovider.ts
@@ -0,0 +1,44 @@
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import {constants} from 'ts/utils/constants';
+
+/*
+ * This class implements the web3-provider-engine subprovider interface and forwards
+ * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected
+ * web3 instance in their browser.
+ * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
+ */
+export class InjectedWeb3SubProvider {
+ private injectedWeb3: Web3;
+ constructor(injectedWeb3: Web3) {
+ this.injectedWeb3 = injectedWeb3;
+ }
+ public handleRequest(payload: any, next: () => void, end: (err: Error, result: any) => void) {
+ switch (payload.method) {
+ case 'web3_clientVersion':
+ this.injectedWeb3.version.getNode(end);
+ return;
+ case 'eth_accounts':
+ this.injectedWeb3.eth.getAccounts(end);
+ return;
+
+ case 'eth_sendTransaction':
+ const [txParams] = payload.params;
+ this.injectedWeb3.eth.sendTransaction(txParams, end);
+ return;
+
+ case 'eth_sign':
+ const [address, message] = payload.params;
+ this.injectedWeb3.eth.sign(address, message, end);
+ return;
+
+ default:
+ next();
+ return;
+ }
+ }
+ // Required to implement this method despite not needing it for this subprovider
+ public setEngine(engine: any) {
+ // noop
+ }
+}
diff --git a/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts
new file mode 100644
index 000000000..df0c5a4db
--- /dev/null
+++ b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts
@@ -0,0 +1,172 @@
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import * as EthereumTx from 'ethereumjs-tx';
+import ethUtil = require('ethereumjs-util');
+import * as ledger from 'ledgerco';
+import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet');
+import {constants} from 'ts/utils/constants';
+import {LedgerEthConnection, SignPersonalMessageParams, TxParams} from 'ts/types';
+
+const NUM_ADDRESSES_TO_FETCH = 10;
+const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
+const SHOULD_GET_CHAIN_CODE = false;
+
+export class LedgerWallet {
+ public isU2FSupported: boolean;
+ public getAccounts: (callback: (err: Error, accounts: string[]) => void) => void;
+ public signMessage: (msgParams: SignPersonalMessageParams,
+ callback: (err: Error, result?: string) => void) => void;
+ public signTransaction: (txParams: TxParams,
+ callback: (err: Error, result?: string) => void) => void;
+ private getNetworkId: () => number;
+ private path: string;
+ private pathIndex: number;
+ private ledgerEthConnection: LedgerEthConnection;
+ private accounts: string[];
+ constructor(getNetworkIdFn: () => number) {
+ this.path = constants.DEFAULT_DERIVATION_PATH;
+ this.pathIndex = 0;
+ this.isU2FSupported = false;
+ this.getNetworkId = getNetworkIdFn;
+ this.getAccounts = this.getAccountsAsync.bind(this);
+ this.signMessage = this.signPersonalMessageAsync.bind(this);
+ this.signTransaction = this.signTransactionAsync.bind(this);
+ }
+ public getPath(): string {
+ return this.path;
+ }
+ public setPath(derivationPath: string) {
+ this.path = derivationPath;
+ // HACK: Must re-assign getAccounts, signMessage and signTransaction since they were
+ // previously bound to old values of this.path
+ this.getAccounts = this.getAccountsAsync.bind(this);
+ this.signMessage = this.signPersonalMessageAsync.bind(this);
+ this.signTransaction = this.signTransactionAsync.bind(this);
+ }
+ public setPathIndex(pathIndex: number) {
+ this.pathIndex = pathIndex;
+ // HACK: Must re-assign signMessage & signTransaction since they it was previously bound to
+ // old values of this.path
+ this.signMessage = this.signPersonalMessageAsync.bind(this);
+ this.signTransaction = this.signTransactionAsync.bind(this);
+ }
+ public async getAccountsAsync(callback: (err: Error, accounts: string[]) => void) {
+ if (!_.isUndefined(this.ledgerEthConnection)) {
+ callback(null, []);
+ return;
+ }
+ this.ledgerEthConnection = await this.createLedgerConnectionAsync();
+
+ const accounts = [];
+ for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) {
+ try {
+ const derivationPath = `${this.path}/${i}`;
+ const result = await this.ledgerEthConnection.getAddress_async(
+ derivationPath, ASK_FOR_ON_DEVICE_CONFIRMATION, SHOULD_GET_CHAIN_CODE,
+ );
+ accounts.push(result.address.toLowerCase());
+ } catch (err) {
+ await this.closeLedgerConnectionAsync();
+ callback(err, null);
+ return;
+ }
+ }
+
+ await this.closeLedgerConnectionAsync();
+ callback(null, accounts);
+ }
+ public async signTransactionAsync(txParams: TxParams, callback: (err: Error, result?: string) => void) {
+ const tx = new EthereumTx(txParams);
+
+ const networkId = this.getNetworkId();
+ const chainId = networkId; // Same thing
+
+ // Set the EIP155 bits
+ tx.raw[6] = Buffer.from([chainId]); // v
+ tx.raw[7] = Buffer.from([]); // r
+ tx.raw[8] = Buffer.from([]); // s
+
+ const txHex = tx.serialize().toString('hex');
+
+ this.ledgerEthConnection = await this.createLedgerConnectionAsync();
+
+ try {
+ const derivationPath = this.getDerivationPath();
+ const result = await this.ledgerEthConnection.signTransaction_async(derivationPath, txHex);
+
+ // Store signature in transaction
+ tx.v = new Buffer(result.v, 'hex');
+ tx.r = new Buffer(result.r, 'hex');
+ tx.s = new Buffer(result.s, 'hex');
+
+ // EIP155: v should be chain_id * 2 + {35, 36}
+ const signedChainId = Math.floor((tx.v[0] - 35) / 2);
+ if (signedChainId !== chainId) {
+ const err = new Error('TOO_OLD_LEDGER_FIRMWARE');
+ callback(err, null);
+ return;
+ }
+
+ const signedTxHex = `0x${tx.serialize().toString('hex')}`;
+ await this.closeLedgerConnectionAsync();
+ callback(null, signedTxHex);
+ } catch (err) {
+ await this.closeLedgerConnectionAsync();
+ callback(err, null);
+ }
+ }
+ public async signPersonalMessageAsync(msgParams: SignPersonalMessageParams,
+ callback: (err: Error, result?: string) => void) {
+ if (!_.isUndefined(this.ledgerEthConnection)) {
+ callback(new Error('Another request is in progress.'));
+ return;
+ }
+ this.ledgerEthConnection = await this.createLedgerConnectionAsync();
+
+ try {
+ const derivationPath = this.getDerivationPath();
+ const result = await this.ledgerEthConnection.signPersonalMessage_async(
+ derivationPath, ethUtil.stripHexPrefix(msgParams.data),
+ );
+ const v = _.parseInt(result.v) - 27;
+ let vHex = v.toString(16);
+ if (vHex.length < 2) {
+ vHex = `0${v}`;
+ }
+ const signature = `0x${result.r}${result.s}${vHex}`;
+ await this.closeLedgerConnectionAsync();
+ callback(null, signature);
+ } catch (err) {
+ await this.closeLedgerConnectionAsync();
+ callback(err, null);
+ }
+ }
+ private async createLedgerConnectionAsync() {
+ if (!_.isUndefined(this.ledgerEthConnection)) {
+ throw new Error('Multiple open connections to the Ledger disallowed.');
+ }
+ const ledgerConnection = await ledger.comm_u2f.create_async();
+ const ledgerEthConnection = new ledger.eth(ledgerConnection);
+ return ledgerEthConnection;
+ }
+ private async closeLedgerConnectionAsync() {
+ if (_.isUndefined(this.ledgerEthConnection)) {
+ return;
+ }
+ await this.ledgerEthConnection.comm.close_async();
+ this.ledgerEthConnection = undefined;
+ }
+ private getDerivationPath() {
+ const derivationPath = `${this.path}/${this.pathIndex}`;
+ return derivationPath;
+ }
+}
+
+export const ledgerWalletSubproviderFactory = (getNetworkIdFn: () => number): LedgerWallet => {
+ const ledgerWallet = new LedgerWallet(getNetworkIdFn);
+ const ledgerWalletSubprovider = new HookedWalletSubprovider(ledgerWallet) as LedgerWallet;
+ ledgerWalletSubprovider.getPath = ledgerWallet.getPath.bind(ledgerWallet);
+ ledgerWalletSubprovider.setPath = ledgerWallet.setPath.bind(ledgerWallet);
+ ledgerWalletSubprovider.setPathIndex = ledgerWallet.setPathIndex.bind(ledgerWallet);
+ return ledgerWalletSubprovider;
+};
diff --git a/packages/website/ts/subproviders/redundant_rpc_subprovider.ts b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts
new file mode 100644
index 000000000..a6c53ebd1
--- /dev/null
+++ b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts
@@ -0,0 +1,41 @@
+import * as _ from 'lodash';
+import {JSONRPCPayload} from 'ts/types';
+import promisify = require('es6-promisify');
+import Subprovider = require('web3-provider-engine/subproviders/subprovider');
+import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
+
+export class RedundantRPCSubprovider extends Subprovider {
+ private rpcs: RpcSubprovider[];
+ constructor(endpoints: string[]) {
+ super();
+ this.rpcs = _.map(endpoints, endpoint => {
+ return new RpcSubprovider({
+ rpcUrl: endpoint,
+ });
+ });
+ }
+ public async handleRequest(payload: JSONRPCPayload, next: () => void,
+ end: (err?: Error, data?: any) => void): Promise<void> {
+ const rpcsCopy = this.rpcs.slice();
+ try {
+ const data = await this.firstSuccessAsync(rpcsCopy, payload, next);
+ end(null, data);
+ } catch (err) {
+ end(err);
+ }
+
+ }
+ private async firstSuccessAsync(rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void): Promise<any> {
+ let lastErr;
+ for (const rpc of rpcs) {
+ try {
+ const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next);
+ return data;
+ } catch (err) {
+ lastErr = err;
+ continue;
+ }
+ }
+ throw Error(lastErr);
+ }
+}
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
new file mode 100644
index 000000000..2d0103499
--- /dev/null
+++ b/packages/website/ts/types.ts
@@ -0,0 +1,692 @@
+import * as _ from 'lodash';
+import BigNumber from 'bignumber.js';
+
+// Utility function to create a K:V from a list of strings
+// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html
+function strEnum(values: string[]): {[key: string]: string} {
+ return _.reduce(values, (result, key) => {
+ result[key] = key;
+ return result;
+ }, Object.create(null));
+}
+
+export enum GenerateOrderSteps {
+ ChooseAssets,
+ GrantAllowance,
+ RemainingConfigs,
+ SignTransaction,
+ CopyAndShare,
+};
+
+export const Side = strEnum([
+ 'receive',
+ 'deposit',
+]);
+export type Side = keyof typeof Side;
+
+export const BlockchainErrs = strEnum([
+ 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK',
+ 'DISCONNECTED_FROM_ETHEREUM_NODE',
+ 'UNHANDLED_ERROR',
+]);
+export type BlockchainErrs = keyof typeof BlockchainErrs;
+
+export const Direction = strEnum([
+ 'forward',
+ 'backward',
+]);
+export type Direction = keyof typeof Direction;
+
+export interface Token {
+ iconUrl?: string;
+ name: string;
+ address: string;
+ symbol: string;
+ decimals: number;
+ isTracked: boolean;
+ isRegistered: boolean;
+};
+
+export interface TokenByAddress {
+ [address: string]: Token;
+};
+
+export interface TokenState {
+ allowance: BigNumber;
+ balance: BigNumber;
+}
+
+export interface TokenStateByAddress {
+ [address: string]: TokenState;
+};
+
+export interface AssetToken {
+ address?: string;
+ amount?: BigNumber;
+}
+
+export interface SideToAssetToken {
+ [side: string]: AssetToken;
+};
+
+export interface SignatureData {
+ hash: string;
+ r: string;
+ s: string;
+ v: number;
+};
+
+export interface HashData {
+ depositAmount: BigNumber;
+ depositTokenContractAddr: string;
+ feeRecipientAddress: string;
+ makerFee: BigNumber;
+ orderExpiryTimestamp: BigNumber;
+ orderMakerAddress: string;
+ orderTakerAddress: string;
+ receiveAmount: BigNumber;
+ receiveTokenContractAddr: string;
+ takerFee: BigNumber;
+ orderSalt: BigNumber;
+}
+
+export interface OrderToken {
+ name: string;
+ symbol: string;
+ decimals: number;
+ address: string;
+}
+
+export interface OrderParty {
+ address: string;
+ token: OrderToken;
+ amount: string;
+ feeAmount: string;
+}
+
+export interface Order {
+ maker: OrderParty;
+ taker: OrderParty;
+ expiration: string;
+ feeRecipient: string;
+ salt: string;
+ signature: SignatureData;
+ exchangeContract: string;
+ networkId: number;
+}
+
+export interface Fill {
+ logIndex: number;
+ maker: string;
+ taker: string;
+ makerToken: string;
+ takerToken: string;
+ filledMakerTokenAmount: BigNumber;
+ filledTakerTokenAmount: BigNumber;
+ paidMakerFee: BigNumber;
+ paidTakerFee: BigNumber;
+ orderHash: string;
+ transactionHash: string;
+ blockTimestamp: number;
+}
+
+export enum BalanceErrs {
+ incorrectNetworkForFaucet,
+ faucetRequestFailed,
+ faucetQueueIsFull,
+ mintingFailed,
+ wethConversionFailed,
+ sendFailed,
+ allowanceSettingFailed,
+};
+
+export const ActionTypes = strEnum([
+ // Portal
+ 'UPDATE_SCREEN_WIDTH',
+ 'UPDATE_NODE_VERSION',
+ 'RESET_STATE',
+ 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS',
+ 'BLOCKCHAIN_ERR_ENCOUNTERED',
+ 'CLEAR_TOKEN_BY_ADDRESS',
+ 'UPDATE_BLOCKCHAIN_IS_LOADED',
+ 'UPDATE_NETWORK_ID',
+ 'UPDATE_GENERATE_ORDER_STEP',
+ 'UPDATE_CHOSEN_ASSET_TOKEN',
+ 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS',
+ 'UPDATE_ORDER_TAKER_ADDRESS',
+ 'UPDATE_ORDER_SALT',
+ 'UPDATE_ORDER_SIGNATURE_DATA',
+ 'UPDATE_TOKEN_BY_ADDRESS',
+ 'REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS',
+ 'UPDATE_TOKEN_STATE_BY_ADDRESS',
+ 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS',
+ 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS',
+ 'REPLACE_TOKEN_BALANCE_BY_ADDRESS',
+ 'UPDATE_TOKEN_BALANCE_BY_ADDRESS',
+ 'UPDATE_ORDER_EXPIRY',
+ 'SWAP_ASSET_TOKENS',
+ 'UPDATE_USER_ADDRESS',
+ 'UPDATE_USER_ETHER_BALANCE',
+ 'UPDATE_USER_SUPPLIED_ORDER_CACHE',
+ 'UPDATE_ORDER_FILL_AMOUNT',
+ 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN',
+
+ // Docs
+ 'UPDATE_LIBRARY_VERSION',
+ 'UPDATE_AVAILABLE_LIBRARY_VERSIONS',
+
+ // Shared
+ 'SHOW_FLASH_MESSAGE',
+ 'HIDE_FLASH_MESSAGE',
+ 'UPDATE_PROVIDER_TYPE',
+ 'UPDATE_INJECTED_PROVIDER_NAME',
+]);
+export type ActionTypes = keyof typeof ActionTypes;
+
+export interface Action {
+ type: ActionTypes;
+ data?: any;
+}
+
+export interface TrackedTokensByNetworkId {
+ [networkId: number]: Token;
+}
+
+export interface Styles {
+ [name: string]: React.CSSProperties;
+}
+
+export interface ProfileInfo {
+ name: string;
+ title?: string;
+ description: string;
+ image: string;
+ linkedIn?: string;
+ github?: string;
+ angellist?: string;
+ medium?: string;
+ twitter?: string;
+}
+
+export interface Partner {
+ name: string;
+ logo: string;
+ url: string;
+}
+
+export interface Statistic {
+ title: string;
+ figure: string;
+}
+
+export interface StatisticByKey {
+ [key: string]: Statistic;
+}
+
+export interface ERC20MarketInfo {
+ etherMarketCapUsd: number;
+ numLiquidERC20Tokens: number;
+ marketCapERC20TokensUsd: number;
+}
+
+export enum ExchangeContractErrs {
+ OrderFillExpired = 'ORDER_FILL_EXPIRED',
+ OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
+ OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
+ OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
+ FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
+ InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE',
+ InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE',
+ InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE',
+ InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE',
+ TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
+ InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
+}
+
+export interface ContractResponse {
+ logs: ContractEvent[];
+}
+
+export interface ContractEvent {
+ event: string;
+ args: any;
+}
+
+export type InputErrMsg = React.ReactNode | string | undefined;
+export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void;
+export const ScreenWidths = strEnum([
+ 'SM',
+ 'MD',
+ 'LG',
+]);
+export type ScreenWidths = keyof typeof ScreenWidths;
+
+export enum AlertTypes {
+ ERROR,
+ SUCCESS,
+}
+
+export const EtherscanLinkSuffixes = strEnum([
+ 'address',
+ 'tx',
+]);
+export type EtherscanLinkSuffixes = keyof typeof EtherscanLinkSuffixes;
+
+export const BlockchainCallErrs = strEnum([
+ 'CONTRACT_DOES_NOT_EXIST',
+ 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
+ 'UNHANDLED_ERROR',
+ 'TOKEN_ADDRESS_IS_INVALID',
+ 'INVALID_SIGNATURE',
+]);
+export type BlockchainCallErrs = keyof typeof BlockchainCallErrs;
+
+export const KindString = strEnum([
+ 'Constructor',
+ 'Property',
+ 'Method',
+ 'Interface',
+ 'Type alias',
+ 'Variable',
+ 'Function',
+ 'Enumeration',
+]);
+export type KindString = keyof typeof KindString;
+
+export interface EnumValue {
+ name: string;
+ defaultValue?: string;
+}
+
+export enum Environments {
+ DEVELOPMENT,
+ PRODUCTION,
+}
+
+export type ContractInstance = any; // TODO: add type definition for Contract
+
+export interface TypeDocType {
+ type: TypeDocTypes;
+ value: string;
+ name: string;
+ types: TypeDocType[];
+ typeArguments?: TypeDocType[];
+ declaration: TypeDocNode;
+ elementType?: TypeDocType;
+}
+
+export interface TypeDocFlags {
+ isStatic?: boolean;
+ isOptional?: boolean;
+ isPublic?: boolean;
+}
+
+export interface TypeDocGroup {
+ title: string;
+ children: number[];
+}
+
+export interface TypeDocNode {
+ id?: number;
+ name?: string;
+ kind?: string;
+ defaultValue?: string;
+ kindString?: string;
+ type?: TypeDocType;
+ fileName?: string;
+ line?: number;
+ comment?: TypeDocNode;
+ text?: string;
+ shortText?: string;
+ returns?: string;
+ declaration: TypeDocNode;
+ flags?: TypeDocFlags;
+ indexSignature?: TypeDocNode[];
+ signatures?: TypeDocNode[];
+ parameters?: TypeDocNode[];
+ typeParameter?: TypeDocNode[];
+ sources?: TypeDocNode[];
+ children?: TypeDocNode[];
+ groups?: TypeDocGroup[];
+}
+
+export enum TypeDocTypes {
+ Intrinsic = 'intrinsic',
+ Reference = 'reference',
+ Array = 'array',
+ StringLiteral = 'stringLiteral',
+ Reflection = 'reflection',
+ Union = 'union',
+ TypeParameter = 'typeParameter',
+ Unknown = 'unknown',
+}
+
+export interface DocAgnosticFormat {
+ [sectionName: string]: DocSection;
+}
+
+export interface DocSection {
+ comment: string;
+ constructors: Array<TypescriptMethod|SolidityMethod>;
+ methods: Array<TypescriptMethod|SolidityMethod>;
+ properties: Property[];
+ types: CustomType[];
+ events?: Event[];
+}
+
+export interface Event {
+ name: string;
+ eventArgs: EventArg[];
+}
+
+export interface EventArg {
+ isIndexed: boolean;
+ name: string;
+ type: Type;
+}
+
+export interface Property {
+ name: string;
+ type: Type;
+ source?: Source;
+ comment?: string;
+}
+
+export interface BaseMethod {
+ isConstructor: boolean;
+ name: string;
+ returnComment?: string|undefined;
+ callPath: string;
+ parameters: Parameter[];
+ returnType: Type;
+ comment?: string;
+}
+
+export interface TypescriptMethod extends BaseMethod {
+ source?: Source;
+ isStatic?: boolean;
+ typeParameter?: TypeParameter;
+}
+
+export interface SolidityMethod extends BaseMethod {
+ isConstant?: boolean;
+ isPayable?: boolean;
+}
+
+export interface Source {
+ fileName: string;
+ line: number;
+}
+
+export interface Parameter {
+ name: string;
+ comment: string;
+ isOptional: boolean;
+ type: Type;
+}
+
+export interface TypeParameter {
+ name: string;
+ type: Type;
+}
+
+export interface Type {
+ name: string;
+ typeDocType: TypeDocTypes;
+ value?: string;
+ typeArguments?: Type[];
+ elementType?: ElementType;
+ types?: Type[];
+ method?: TypescriptMethod;
+}
+
+export interface ElementType {
+ name: string;
+ typeDocType: TypeDocTypes;
+}
+
+export interface IndexSignature {
+ keyName: string;
+ keyType: Type;
+ valueName: string;
+}
+
+export interface CustomType {
+ name: string;
+ kindString: string;
+ type?: Type;
+ method?: TypescriptMethod;
+ indexSignature?: IndexSignature;
+ defaultValue?: string;
+ comment?: string;
+ children?: CustomTypeChild[];
+}
+
+export interface CustomTypeChild {
+ name: string;
+ type?: Type;
+ defaultValue?: string;
+}
+
+export const ZeroExJsDocSections = strEnum([
+ 'introduction',
+ 'installation',
+ 'testrpc',
+ 'async',
+ 'errors',
+ 'versioning',
+ 'zeroEx',
+ 'exchange',
+ 'token',
+ 'tokenRegistry',
+ 'etherToken',
+ 'proxy',
+ 'types',
+]);
+export type ZeroExJsDocSections = keyof typeof ZeroExJsDocSections;
+
+export const SmartContractsDocSections = strEnum([
+ 'Introduction',
+ 'Exchange',
+ 'TokenTransferProxy',
+ 'TokenRegistry',
+ 'ZRXToken',
+ 'EtherToken',
+]);
+export type SmartContractsDocSections = keyof typeof SmartContractsDocSections;
+
+export interface FAQQuestion {
+ prompt: string;
+ answer: React.ReactNode;
+}
+export interface FAQSection {
+ name: string;
+ questions: FAQQuestion[];
+}
+
+export interface S3FileObject {
+ Key: {
+ _text: string;
+ };
+}
+
+export interface MenuSubsectionsBySection {
+ [section: string]: string[];
+}
+
+export const ProviderType = strEnum([
+ 'INJECTED',
+ 'LEDGER',
+]);
+export type ProviderType = keyof typeof ProviderType;
+
+export interface Fact {
+ title: string;
+ explanation: string;
+ image: string;
+}
+
+interface LedgerGetAddressResult {
+ address: string;
+}
+interface LedgerSignResult {
+ v: string;
+ r: string;
+ s: string;
+}
+interface LedgerCommunication {
+ close_async: () => void;
+}
+export interface LedgerEthConnection {
+ getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean,
+ shouldGetChainCode: boolean) => Promise<LedgerGetAddressResult>;
+ signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<LedgerSignResult>;
+ signTransaction_async: (derivationPath: string, txHex: string) => Promise<LedgerSignResult>;
+ comm: LedgerCommunication;
+}
+export interface SignPersonalMessageParams {
+ data: string;
+}
+
+export interface LedgerWalletSubprovider {
+ getPath: () => string;
+ setPath: (path: string) => void;
+ setPathIndex: (pathIndex: number) => void;
+}
+
+export interface TxParams {
+ nonce: string;
+ gasPrice?: number;
+ gasLimit: string;
+ to: string;
+ value?: string;
+ data?: string;
+ chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
+}
+
+export interface PublicNodeUrlsByNetworkId {
+ [networkId: number]: string[];
+};
+
+export interface JSONRPCPayload {
+ params: any[];
+ method: string;
+}
+
+export interface BlogPost {
+ image: string;
+ date: string;
+ title: string;
+ description: string;
+ url: string;
+}
+
+export interface TypeDefinitionByName {
+ [typeName: string]: CustomType;
+}
+
+export interface Article {
+ section: string;
+ title: string;
+ content: string;
+ fileName: string;
+}
+
+export interface ArticlesBySection {
+ [section: string]: Article[];
+}
+
+export interface DialogConfigs {
+ title: string;
+ isModal: boolean;
+ actions: any[];
+}
+
+export enum TokenVisibility {
+ ALL = 'ALL',
+ UNTRACKED = 'UNTRACKED',
+ TRACKED = 'TRACKED',
+}
+
+export enum HeaderSizes {
+ H1 = 'h1',
+ H2 = 'h2',
+ H3 = 'h3',
+}
+
+export interface DoxityDocObj {
+ [contractName: string]: DoxityContractObj;
+}
+
+export interface DoxityContractObj {
+ title: string;
+ fileName: string;
+ name: string;
+ abiDocs: DoxityAbiDoc[];
+}
+
+export interface DoxityAbiDoc {
+ constant: boolean;
+ inputs: DoxityInput[];
+ name: string;
+ outputs: DoxityOutput[];
+ payable: boolean;
+ type: string;
+ details?: string;
+ return?: string;
+}
+
+export interface DoxityOutput {
+ name: string;
+ type: string;
+}
+
+export interface DoxityInput {
+ name: string;
+ type: string;
+ description: string;
+ indexed?: boolean;
+}
+
+export interface VersionToFileName {
+ [version: string]: string;
+}
+
+export enum Docs {
+ ZeroExJs,
+ SmartContracts,
+}
+
+export interface ContractAddresses {
+ [version: string]: {
+ [network: string]: AddressByContractName;
+ };
+}
+
+export interface AddressByContractName {
+ [contractName: string]: string;
+}
+
+export enum Networks {
+ mainnet = 'Mainnet',
+ kovan = 'Kovan',
+ ropsten = 'Ropsten',
+ rinkeby = 'Rinkeby',
+}
+
+export enum AbiTypes {
+ Constructor = 'constructor',
+ Function = 'function',
+ Event = 'event',
+}
+
+export enum WebsitePaths {
+ Portal = '/portal',
+ Wiki = '/wiki',
+ ZeroExJs = '/docs/0xjs',
+ Home = '/',
+ FAQ = '/faq',
+ About = '/about',
+ Whitepaper = '/pdfs/0x_white_paper.pdf',
+ SmartContracts = '/docs/contracts',
+}
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
new file mode 100644
index 000000000..49ac4b5e0
--- /dev/null
+++ b/packages/website/ts/utils/configs.ts
@@ -0,0 +1,18 @@
+import * as _ from 'lodash';
+import {Environments} from 'ts/types';
+
+const BASE_URL = window.location.origin;
+const isDevelopment = _.includes(BASE_URL, 'https://0xproject.dev:3572') ||
+ _.includes(BASE_URL, 'https://localhost:3572') ||
+ _.includes(BASE_URL, 'https://127.0.0.1');
+
+export const configs = {
+ BASE_URL,
+ ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION,
+ BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com',
+ symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'],
+ // WARNING: ZRX & WETH MUST always be default trackedTokens
+ defaultTrackedTokenSymbols: ['WETH', 'ZRX'],
+ lastLocalStorageFillClearanceDate: '2017-09-09',
+ isMainnetEnabled: true,
+};
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
new file mode 100644
index 000000000..42b80795e
--- /dev/null
+++ b/packages/website/ts/utils/constants.ts
@@ -0,0 +1,270 @@
+import {
+ ExchangeContractErrs,
+ PublicNodeUrlsByNetworkId,
+ ZeroExJsDocSections,
+ SmartContractsDocSections,
+ Docs,
+ ContractAddresses,
+ Networks,
+ WebsitePaths,
+} from 'ts/types';
+import BigNumber from 'bignumber.js';
+
+const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
+
+export const constants = {
+ ANGELLIST_URL: 'https://angel.co/0xproject/jobs',
+ STAGING_DOMAIN: 'staging-0xproject.s3-website-us-east-1.amazonaws.com',
+ PRODUCTION_DOMAIN: '0xproject.com',
+ DEVELOPMENT_DOMAIN: '0xproject.dev:3572',
+ BIGNUMBERJS_GITHUB_URL: 'http://mikemcl.github.io/bignumber.js',
+ BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
+ BITLY_ENDPOINT: 'https://api-ssl.bitly.com',
+ BLOG_URL: 'https://blog.0xproject.com/latest',
+ CUSTOM_BLUE: '#60a4f4',
+ DEFAULT_DERIVATION_PATH: `44'/60'/0'`,
+ ETHER_FAUCET_ENDPOINT: 'https://faucet.0xproject.com',
+ FEE_RECIPIENT_ADDRESS: '0x0000000000000000000000000000000000000000',
+ FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/',
+ GITHUB_URL: 'https://github.com/0xProject',
+ GITHUB_0X_JS_URL: 'https://github.com/0xProject/0x.js',
+ GITHUB_CONTRACTS_URL: 'https://github.com/0xProject/contracts',
+ GITHUB_WIKI_URL: 'https://github.com/0xProject/wiki',
+ HTTP_NO_CONTENT_STATUS_CODE: 204,
+ ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY: 'didAcceptPortalDisclaimer',
+ LINKEDIN_0X_URL: 'https://www.linkedin.com/company/0x',
+ LEDGER_PROVIDER_NAME: 'Ledger',
+ METAMASK_PROVIDER_NAME: 'Metamask',
+ GENESIS_ORDER_BLOCK_BY_NETWORK_ID: {
+ 1: 4145578,
+ 42: 3117574,
+ 50: 0,
+ } as {[networkId: number]: number},
+ PUBLIC_PROVIDER_NAME: '0x Public',
+ // The order matters. We first try first node and only then fall back to others.
+ PUBLIC_NODE_URLS_BY_NETWORK_ID: {
+ [1]: [
+ `https://mainnet.infura.io/${INFURA_API_KEY}`,
+ ],
+ [42]: [
+ `https://kovan.infura.io/${INFURA_API_KEY}`,
+ ],
+ } as PublicNodeUrlsByNetworkId,
+ PARITY_SIGNER_PROVIDER_NAME: 'Parity Signer',
+ GENERIC_PROVIDER_NAME: 'Injected Web3',
+ MAKER_FEE: new BigNumber(0),
+ MAINNET_NAME: 'Main network',
+ MAINNET_NETWORK_ID: 1,
+ METAMASK_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+ // tslint:disable-next-line:max-line-length
+ PARITY_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig',
+ MIST_DOWNLOAD_URL: 'https://github.com/ethereum/mist/releases',
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65',
+ DOCS_SCROLL_DURATION_MS: 0,
+ DOCS_CONTAINER_ID: 'documentation',
+ HOME_SCROLL_DURATION_MS: 500,
+ REDDIT_URL: 'https://reddit.com/r/0xproject',
+ STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md',
+ SUCCESS_STATUS: 200,
+ S3_0XJS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/0xjs-docs-jsons',
+ S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/smart-contracts-docs-json',
+ UNAVAILABLE_STATUS: 503,
+ TAKER_FEE: new BigNumber(0),
+ TESTNET_NAME: 'Kovan',
+ TESTNET_NETWORK_ID: 42,
+ TESTRPC_NETWORK_ID: 50,
+ TWITTER_URL: 'https://twitter.com/0xproject',
+ ETH_DECIMAL_PLACES: 18,
+ MINT_AMOUNT: new BigNumber('100000000000000000000'),
+ WEB3_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API',
+ WEB3_PROVIDER_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API#example-7',
+ ZEROEX_CHAT_URL: 'https://chat.0xproject.com',
+ // Projects
+ ETHFINEX_URL: 'https://www.bitfinex.com/ethfinex',
+ RADAR_RELAY_URL: 'https://radarrelay.com',
+ PARADEX_URL: 'https://paradex.io',
+ DYDX_URL: 'https://dydx.exchange',
+ MELONPORT_URL: 'https://melonport.com',
+ DISTRICT_0X_URL: 'https://district0x.io',
+ DHARMA_URL: 'https://dharma.io',
+ LENDROID_URL: 'https://lendroid.com',
+ MAKER_URL: 'https://makerdao.com',
+ ARAGON_URL: 'https://aragon.one',
+ BLOCKNET_URL: 'https://blocknet.co',
+ OCEAN_URL: 'http://the0cean.com',
+ STATUS_URL: 'https://status.im',
+ AUGUR_URL: 'https://augur.net',
+ AUCTUS_URL: 'https://auctus.org',
+ OPEN_ANX_URL: 'https://www.openanx.org',
+
+ iconUrlBySymbol: {
+ 'REP': '/images/token_icons/augur.png',
+ 'DGD': '/images/token_icons/digixdao.png',
+ 'WETH': '/images/token_icons/ether_erc20.png',
+ 'MLN': '/images/token_icons/melon.png',
+ 'GNT': '/images/token_icons/golem.png',
+ 'MKR': '/images/token_icons/makerdao.png',
+ 'ZRX': '/images/token_icons/zero_ex.png',
+ 'ANT': '/images/token_icons/aragon.png',
+ 'BNT': '/images/token_icons/bancor.png',
+ 'BAT': '/images/token_icons/basicattentiontoken.png',
+ 'CVC': '/images/token_icons/civic.png',
+ 'EOS': '/images/token_icons/eos.png',
+ 'FUN': '/images/token_icons/funfair.png',
+ 'GNO': '/images/token_icons/gnosis.png',
+ 'ICN': '/images/token_icons/iconomi.png',
+ 'OMG': '/images/token_icons/omisego.png',
+ 'SNT': '/images/token_icons/status.png',
+ 'STORJ': '/images/token_icons/storjcoinx.png',
+ 'PAY': '/images/token_icons/tenx.png',
+ 'QTUM': '/images/token_icons/qtum.png',
+ 'DNT': '/images/token_icons/district0x.png',
+ 'SNGLS': '/images/token_icons/singularity.png',
+ 'EDG': '/images/token_icons/edgeless.png',
+ '1ST': '/images/token_icons/firstblood.jpg',
+ 'WINGS': '/images/token_icons/wings.png',
+ 'BQX': '/images/token_icons/bitquence.png',
+ 'LUN': '/images/token_icons/lunyr.png',
+ 'RLC': '/images/token_icons/iexec.png',
+ 'MCO': '/images/token_icons/monaco.png',
+ 'ADT': '/images/token_icons/adtoken.png',
+ 'CFI': '/images/token_icons/cofound-it.png',
+ 'ROL': '/images/token_icons/etheroll.png',
+ 'WGNT': '/images/token_icons/golem.png',
+ 'MTL': '/images/token_icons/metal.png',
+ 'NMR': '/images/token_icons/numeraire.png',
+ 'SAN': '/images/token_icons/santiment.png',
+ 'TAAS': '/images/token_icons/taas.png',
+ 'TKN': '/images/token_icons/tokencard.png',
+ 'TRST': '/images/token_icons/trust.png',
+ } as {[symbol: string]: string},
+ networkNameById: {
+ 1: Networks.mainnet,
+ 3: Networks.ropsten,
+ 4: Networks.rinkeby,
+ 42: Networks.kovan,
+ } as {[symbol: number]: string},
+ networkIdByName: {
+ [Networks.mainnet]: 1,
+ [Networks.ropsten]: 3,
+ [Networks.rinkeby]: 4,
+ [Networks.kovan]: 42,
+ } as {[networkName: string]: number},
+ // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is
+ // currently no way to extract the re-exported types from index.ts via TypeDoc :(
+ public0xjsTypes: [
+ 'Order',
+ 'SignedOrder',
+ 'ECSignature',
+ 'ZeroExError',
+ 'EventCallback',
+ 'EventCallbackAsync',
+ 'EventCallbackSync',
+ 'ExchangeContractErrs',
+ 'ContractEvent',
+ 'Token',
+ 'ExchangeEvents',
+ 'IndexedFilterValues',
+ 'SubscriptionOpts',
+ 'BlockParam',
+ 'OrderFillOrKillRequest',
+ 'OrderCancellationRequest',
+ 'OrderFillRequest',
+ 'ContractEventEmitter',
+ 'Web3Provider',
+ 'ContractEventArgs',
+ 'LogCancelArgs',
+ 'LogFillArgs',
+ 'LogErrorContractEventArgs',
+ 'LogFillContractEventArgs',
+ 'LogCancelContractEventArgs',
+ 'TokenEvents',
+ 'ExchangeContractEventArgs',
+ 'TransferContractEventArgs',
+ 'ApprovalContractEventArgs',
+ 'TokenContractEventArgs',
+ 'ZeroExConfig',
+ 'TransactionReceiptWithDecodedLogs',
+ 'LogWithDecodedArgs',
+ 'DecodedLogArgs',
+ 'MethodOpts',
+ 'ValidateOrderFillableOpts',
+ 'OrderTransactionOpts',
+ 'ContractEventArg',
+ 'LogEvent',
+ 'LogEntry',
+ 'DecodedLogEvent',
+ ],
+ menuSmartContracts: {
+ introduction: [
+ SmartContractsDocSections.Introduction,
+ ],
+ contracts: [
+ SmartContractsDocSections.Exchange,
+ SmartContractsDocSections.TokenRegistry,
+ SmartContractsDocSections.ZRXToken,
+ SmartContractsDocSections.EtherToken,
+ SmartContractsDocSections.TokenTransferProxy,
+ ],
+ },
+ menu0xjs: {
+ introduction: [
+ ZeroExJsDocSections.introduction,
+ ],
+ install: [
+ ZeroExJsDocSections.installation,
+ ],
+ topics: [
+ ZeroExJsDocSections.async,
+ ZeroExJsDocSections.errors,
+ ZeroExJsDocSections.versioning,
+ ],
+ zeroEx: [
+ ZeroExJsDocSections.zeroEx,
+ ],
+ contracts: [
+ ZeroExJsDocSections.exchange,
+ ZeroExJsDocSections.token,
+ ZeroExJsDocSections.tokenRegistry,
+ ZeroExJsDocSections.etherToken,
+ ZeroExJsDocSections.proxy,
+ ],
+ types: [
+ ZeroExJsDocSections.types,
+ ],
+ },
+ menuSubsectionToVersionWhenIntroduced: {
+ [ZeroExJsDocSections.etherToken]: '0.7.1',
+ [ZeroExJsDocSections.proxy]: '0.8.0',
+ },
+ docToPath: {
+ [Docs.ZeroExJs]: WebsitePaths.ZeroExJs,
+ [Docs.SmartContracts]: WebsitePaths.SmartContracts,
+ },
+ contractAddresses: {
+ '1.0.0': {
+ [Networks.mainnet]: {
+ [SmartContractsDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
+ [SmartContractsDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
+ [SmartContractsDocSections.EtherToken]: '0x2956356cd2a2bf3202f771f50d3d14a367b48070',
+ [SmartContractsDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
+ },
+ [Networks.ropsten]: {
+ [SmartContractsDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
+ [SmartContractsDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
+ [SmartContractsDocSections.EtherToken]: '0xc00fd9820cd2898cc4c054b7bf142de637ad129a',
+ [SmartContractsDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
+ },
+ [Networks.kovan]: {
+ [SmartContractsDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
+ [SmartContractsDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
+ [SmartContractsDocSections.EtherToken]: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c',
+ [SmartContractsDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f',
+ },
+ },
+ } as ContractAddresses,
+};
diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts
new file mode 100644
index 000000000..ca6e0dc52
--- /dev/null
+++ b/packages/website/ts/utils/doc_utils.ts
@@ -0,0 +1,55 @@
+import * as _ from 'lodash';
+import findVersions = require('find-versions');
+import convert = require('xml-js');
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {VersionToFileName, S3FileObject, TypeDocNode, DoxityDocObj} from 'ts/types';
+
+export const docUtils = {
+ async getVersionToFileNameAsync(s3DocJsonRoot: string):
+ Promise<VersionToFileName> {
+ const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot);
+ const versionToFileName: VersionToFileName = {};
+ _.each(versionFileNames, fileName => {
+ const [version] = findVersions(fileName);
+ versionToFileName[version] = fileName;
+ });
+ return versionToFileName;
+ },
+ async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> {
+ const response = await fetch(s3DocJsonRoot);
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the docs fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`);
+ return;
+ }
+ const responseXML = await response.text();
+ const responseJSONString = convert.xml2json(responseXML, {
+ compact: true,
+ });
+ const responseObj = JSON.parse(responseJSONString);
+ let fileObjs: S3FileObject[];
+ if (_.isArray(responseObj.ListBucketResult.Contents)) {
+ fileObjs = responseObj.ListBucketResult.Contents as S3FileObject[];
+ } else {
+ fileObjs = [responseObj.ListBucketResult.Contents];
+ }
+ const versionFileNames = _.map(fileObjs, fileObj => {
+ return fileObj.Key._text;
+ });
+ return versionFileNames;
+ },
+ async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode|DoxityDocObj> {
+ const endpoint = `${s3DocJsonRoot}/${fileName}`;
+ const response = await fetch(endpoint);
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the docs fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`);
+ return;
+ }
+ const jsonDocObj = await response.json();
+ return jsonDocObj;
+ },
+};
diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts
new file mode 100644
index 000000000..3bab0a69d
--- /dev/null
+++ b/packages/website/ts/utils/doxity_utils.ts
@@ -0,0 +1,162 @@
+import * as _ from 'lodash';
+import {
+ DoxityDocObj,
+ DoxityContractObj,
+ DoxityAbiDoc,
+ DoxityInput,
+ DocAgnosticFormat,
+ DocSection,
+ Parameter,
+ Property,
+ Type,
+ TypeDocTypes,
+ EventArg,
+ AbiTypes,
+ SolidityMethod,
+} from 'ts/types';
+
+export const doxityUtils = {
+ convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat {
+ const docAgnosticFormat: DocAgnosticFormat = {};
+ _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => {
+ const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return abiDoc.type === AbiTypes.Constructor;
+ });
+ const constructors = [];
+ if (!_.isUndefined(doxityConstructor)) {
+ const constructor = {
+ isConstructor: true,
+ name: doxityContractObj.name,
+ comment: doxityConstructor.details,
+ returnComment: doxityConstructor.return,
+ callPath: '',
+ parameters: this._convertParameters(doxityConstructor.inputs),
+ returnType: this._convertType(doxityContractObj.name),
+ };
+ constructors.push(constructor);
+ }
+
+ const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>
+ (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return this._isMethod(abiDoc);
+ });
+ const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>(doxityMethods,
+ (doxityMethod: DoxityAbiDoc) => {
+ // We assume that none of our functions returns more then a single value
+ const outputIfExists = !_.isUndefined(doxityMethod.outputs) ?
+ doxityMethod.outputs[0] :
+ undefined;
+ const returnTypeIfExists = !_.isUndefined(outputIfExists) ?
+ this._convertType(outputIfExists.type) :
+ undefined;
+ // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken
+ const callPath = contractName !== 'ZRXToken' ?
+ `${contractName[0].toLowerCase()}${contractName.slice(1)}.` :
+ `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`;
+ const method = {
+ isConstructor: false,
+ isConstant: doxityMethod.constant,
+ isPayable: doxityMethod.payable,
+ name: doxityMethod.name,
+ comment: doxityMethod.details,
+ returnComment: doxityMethod.return,
+ callPath,
+ parameters: this._convertParameters(doxityMethod.inputs),
+ returnType: returnTypeIfExists,
+ };
+ return method;
+ });
+
+ const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>
+ (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return this._isProperty(abiDoc);
+ });
+ const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => {
+ // We assume that none of our functions return more then a single return value
+ let typeName = doxityProperty.outputs[0].type;
+ if (!_.isEmpty(doxityProperty.inputs)) {
+ // Properties never have more then a single input
+ typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`;
+ }
+ const property = {
+ name: doxityProperty.name,
+ type: this._convertType(typeName),
+ comment: doxityProperty.details,
+ };
+ return property;
+ });
+
+ const doxityEvents = _.filter(
+ doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event,
+ );
+ const events = _.map(doxityEvents, doxityEvent => {
+ const event = {
+ name: doxityEvent.name,
+ eventArgs: this._convertEventArgs(doxityEvent.inputs),
+ };
+ return event;
+ });
+
+ const docSection: DocSection = {
+ comment: doxityContractObj.title,
+ constructors,
+ methods,
+ properties,
+ types: [],
+ events,
+ };
+ docAgnosticFormat[contractName] = docSection;
+ });
+ return docAgnosticFormat;
+ },
+ _convertParameters(inputs: DoxityInput[]): Parameter[] {
+ const parameters = _.map(inputs, input => {
+ const parameter = {
+ name: input.name,
+ comment: input.description,
+ isOptional: false,
+ type: this._convertType(input.type),
+ };
+ return parameter;
+ });
+ return parameters;
+ },
+ _convertType(typeName: string): Type {
+ const type = {
+ name: typeName,
+ typeDocType: TypeDocTypes.Intrinsic,
+ };
+ return type;
+ },
+ _isMethod(abiDoc: DoxityAbiDoc) {
+ if (abiDoc.type !== AbiTypes.Function) {
+ return false;
+ }
+ const hasInputs = !_.isEmpty(abiDoc.inputs);
+ const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
+ const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
+ const isMethod = hasNamedOutputIfExists && !isNameAllCaps;
+ return isMethod;
+ },
+ _isProperty(abiDoc: DoxityAbiDoc) {
+ if (abiDoc.type !== AbiTypes.Function) {
+ return false;
+ }
+ const hasInputs = !_.isEmpty(abiDoc.inputs);
+ const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
+ const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
+ const isProperty = !hasNamedOutputIfExists || isNameAllCaps;
+ return isProperty;
+ },
+ _convertEventArgs(inputs: DoxityInput[]): EventArg[] {
+ const eventArgs = _.map(inputs, input => {
+ const eventArg = {
+ isIndexed: input.indexed,
+ name: input.name,
+ type: this._convertType(input.type),
+ };
+ return eventArg;
+ });
+ return eventArgs;
+ },
+};
diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts
new file mode 100644
index 000000000..a9731c4d4
--- /dev/null
+++ b/packages/website/ts/utils/error_reporter.ts
@@ -0,0 +1,52 @@
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {Environments} from 'ts/types';
+
+// Suggested way to include Rollbar with Webpack
+// https://github.com/rollbar/rollbar.js/tree/master/examples/webpack
+const rollbarConfig = {
+ accessToken: constants.ROLLBAR_ACCESS_TOKEN,
+ captureUncaught: true,
+ captureUnhandledRejections: true,
+ itemsPerMinute: 10,
+ maxItems: 500,
+ payload: {
+ environment: configs.ENVIRONMENT,
+ },
+ uncaughtErrorLevel: 'error',
+ hostWhiteList: [constants.PRODUCTION_DOMAIN, constants.STAGING_DOMAIN],
+ ignoredMessages: [
+ // Errors from the third-party scripts
+ 'Script error',
+ // Network errors or ad-blockers
+ 'TypeError: Failed to fetch',
+ 'Exchange has not been deployed to detected network (network/artifact mismatch)',
+ // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE
+ 'undefined is not an object (evaluating \'__gCrWeb.autofill.extractForms\')',
+ // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging
+ 'SecurityError (DOM Exception 18)',
+ ],
+};
+import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js');
+const rollbar = Rollbar.init(rollbarConfig);
+
+export const errorReporter = {
+ reportAsync(err: Error): Promise<any> {
+ if (configs.ENVIRONMENT === Environments.DEVELOPMENT) {
+ return; // Let's not log development errors to rollbar
+ }
+
+ return new Promise((resolve, reject) => {
+ rollbar.error(err, (rollbarErr: Error) => {
+ if (rollbarErr) {
+ utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
+ // We never want to reject and cause the app to throw because of rollbar
+ resolve();
+ } else {
+ resolve();
+ }
+ });
+ });
+ },
+};
diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts
new file mode 100644
index 000000000..b3d0f7d90
--- /dev/null
+++ b/packages/website/ts/utils/typedoc_utils.ts
@@ -0,0 +1,356 @@
+import * as _ from 'lodash';
+import compareVersions = require('compare-versions');
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {
+ TypeDocNode,
+ KindString,
+ ZeroExJsDocSections,
+ MenuSubsectionsBySection,
+ TypeDocType,
+ Type,
+ DocAgnosticFormat,
+ DocSection,
+ TypescriptMethod,
+ Parameter,
+ Property,
+ CustomType,
+ IndexSignature,
+ CustomTypeChild,
+ TypeParameter,
+ TypeDocTypes,
+} from 'ts/types';
+
+const TYPES_MODULE_PATH = '"src/types"';
+
+export const sectionNameToPossibleModulePaths: {[name: string]: string[]} = {
+ [ZeroExJsDocSections.zeroEx]: ['"src/0x"'],
+ [ZeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'],
+ [ZeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'],
+ [ZeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'],
+ [ZeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'],
+ [ZeroExJsDocSections.proxy]: [
+ '"src/contract_wrappers/proxy_wrapper"',
+ '"src/contract_wrappers/token_transfer_proxy_wrapper"',
+ ],
+ [ZeroExJsDocSections.types]: [TYPES_MODULE_PATH],
+};
+
+export const typeDocUtils = {
+ isType(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Interface ||
+ entity.kindString === KindString.Function ||
+ entity.kindString === KindString['Type alias'] ||
+ entity.kindString === KindString.Variable ||
+ entity.kindString === KindString.Enumeration;
+ },
+ isMethod(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Method;
+ },
+ isConstructor(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Constructor;
+ },
+ isProperty(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Property;
+ },
+ isPrivateOrProtectedProperty(propertyName: string): boolean {
+ return _.startsWith(propertyName, '_');
+ },
+ isPublicType(typeName: string): boolean {
+ return _.includes(constants.public0xjsTypes, typeName);
+ },
+ getModuleDefinitionBySectionNameIfExists(versionDocObj: TypeDocNode, sectionName: string):
+ TypeDocNode|undefined {
+ const possibleModulePathNames = sectionNameToPossibleModulePaths[sectionName];
+ const modules = versionDocObj.children;
+ for (const mod of modules) {
+ if (_.includes(possibleModulePathNames, mod.name)) {
+ const moduleWithName = mod;
+ return moduleWithName;
+ }
+ }
+ return undefined;
+ },
+ getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
+ const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
+ if (_.isUndefined(docAgnosticFormat)) {
+ return menuSubsectionsBySection;
+ }
+
+ const docSections = _.keys(ZeroExJsDocSections);
+ _.each(docSections, sectionName => {
+ const docSection = docAgnosticFormat[sectionName];
+ if (_.isUndefined(docSection)) {
+ return; // no-op
+ }
+
+ if (sectionName === ZeroExJsDocSections.types) {
+ const typeNames = _.map(docSection.types, t => t.name);
+ menuSubsectionsBySection[sectionName] = typeNames;
+ } else {
+ const methodNames = _.map(docSection.methods, m => m.name);
+ menuSubsectionsBySection[sectionName] = methodNames;
+ }
+ });
+ return menuSubsectionsBySection;
+ },
+ getFinal0xjsMenu(selectedVersion: string): {[section: string]: string[]} {
+ const finalMenu = _.cloneDeep(constants.menu0xjs);
+ finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => {
+ const versionIntroducedIfExists = constants.menuSubsectionToVersionWhenIntroduced[contractName];
+ if (!_.isUndefined(versionIntroducedIfExists)) {
+ const existsInSelectedVersion = compareVersions(selectedVersion,
+ versionIntroducedIfExists) >= 0;
+ return existsInSelectedVersion;
+ } else {
+ return true;
+ }
+ });
+ return finalMenu;
+ },
+ convertToDocAgnosticFormat(typeDocJson: TypeDocNode): DocAgnosticFormat {
+ const subMenus = _.values(constants.menu0xjs);
+ const orderedSectionNames = _.flatten(subMenus);
+ const docAgnosticFormat: DocAgnosticFormat = {};
+ _.each(orderedSectionNames, sectionName => {
+ const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists(
+ typeDocJson, sectionName,
+ );
+ if (_.isUndefined(packageDefinitionIfExists)) {
+ return; // no-op
+ }
+
+ // Since the `types.ts` file is the only file that does not export a module/class but
+ // instead has each type export itself, we do not need to go down two levels of nesting
+ // for it.
+ let entities;
+ let packageComment = '';
+ if (sectionName === ZeroExJsDocSections.types) {
+ entities = packageDefinitionIfExists.children;
+ } else {
+ entities = packageDefinitionIfExists.children[0].children;
+ const commentObj = packageDefinitionIfExists.children[0].comment;
+ packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
+ }
+
+ const docSection = typeDocUtils._convertEntitiesToDocSection(entities, sectionName);
+ docSection.comment = packageComment;
+ docAgnosticFormat[sectionName] = docSection;
+ });
+ return docAgnosticFormat;
+ },
+ _convertEntitiesToDocSection(entities: TypeDocNode[], sectionName: string) {
+ const docSection: DocSection = {
+ comment: '',
+ constructors: [],
+ methods: [],
+ properties: [],
+ types: [],
+ };
+
+ let isConstructor;
+ _.each(entities, entity => {
+ switch (entity.kindString) {
+ case KindString.Constructor:
+ isConstructor = true;
+ const constructor = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
+ docSection.constructors.push(constructor);
+ break;
+
+ case KindString.Method:
+ if (entity.flags.isPublic) {
+ isConstructor = false;
+ const method = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
+ docSection.methods.push(method);
+ }
+ break;
+
+ case KindString.Property:
+ if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) {
+ const property = typeDocUtils._convertProperty(entity, sectionName);
+ docSection.properties.push(property);
+ }
+ break;
+
+ case KindString.Interface:
+ case KindString.Function:
+ case KindString.Variable:
+ case KindString.Enumeration:
+ case KindString['Type alias']:
+ if (typeDocUtils.isPublicType(entity.name)) {
+ const customType = typeDocUtils._convertCustomType(entity, sectionName);
+ docSection.types.push(customType);
+ }
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('kindString', entity.kindString);
+ }
+ });
+ return docSection;
+ },
+ _convertCustomType(entity: TypeDocNode, sectionName: string): CustomType {
+ const typeIfExists = !_.isUndefined(entity.type) ?
+ typeDocUtils._convertType(entity.type, sectionName) :
+ undefined;
+ const isConstructor = false;
+ const methodIfExists = !_.isUndefined(entity.declaration) ?
+ typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
+ undefined;
+ const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) ?
+ typeDocUtils._convertIndexSignature(entity.indexSignature[0], sectionName) :
+ undefined;
+ const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ?
+ entity.comment.shortText :
+ undefined;
+
+ const childrenIfExist = !_.isUndefined(entity.children) ?
+ _.map(entity.children, (child: TypeDocNode) => {
+ const childTypeIfExists = !_.isUndefined(child.type) ?
+ typeDocUtils._convertType(child.type, sectionName) :
+ undefined;
+ const c: CustomTypeChild = {
+ name: child.name,
+ type: childTypeIfExists,
+ defaultValue: child.defaultValue,
+ };
+ return c;
+ }) :
+ undefined;
+
+ const customType = {
+ name: entity.name,
+ kindString: entity.kindString,
+ type: typeIfExists,
+ method: methodIfExists,
+ indexSignature: indexSignatureIfExists,
+ defaultValue: entity.defaultValue,
+ comment: commentIfExists,
+ children: childrenIfExist,
+ };
+ return customType;
+ },
+ _convertIndexSignature(entity: TypeDocNode, sectionName: string): IndexSignature {
+ const key = entity.parameters[0];
+ const indexSignature = {
+ keyName: key.name,
+ keyType: typeDocUtils._convertType(key.type, sectionName),
+ valueName: entity.type.name,
+ };
+ return indexSignature;
+ },
+ _convertProperty(entity: TypeDocNode, sectionName: string): Property {
+ const source = entity.sources[0];
+ const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined;
+ const property = {
+ name: entity.name,
+ type: typeDocUtils._convertType(entity.type, sectionName),
+ source: {
+ fileName: source.fileName,
+ line: source.line,
+ },
+ comment: commentIfExists,
+ };
+ return property;
+ },
+ _convertMethod(entity: TypeDocNode, isConstructor: boolean, sectionName: string): TypescriptMethod {
+ const signature = entity.signatures[0];
+ const source = entity.sources[0];
+ const hasComment = !_.isUndefined(signature.comment);
+ const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic;
+
+ const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.';
+ // HACK: we use the fact that the sectionName is the same as the property name at the top-level
+ // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON.
+ let callPath = (sectionName !== ZeroExJsDocSections.zeroEx) ?
+ `${topLevelInterface}${sectionName}.` :
+ topLevelInterface;
+ callPath = isConstructor ? '' : callPath;
+
+ const parameters = _.map(signature.parameters, param => {
+ return typeDocUtils._convertParameter(param, sectionName);
+ });
+ const returnType = typeDocUtils._convertType(signature.type, sectionName);
+ const typeParameter = _.isUndefined(signature.typeParameter) ?
+ undefined :
+ typeDocUtils._convertTypeParameter(signature.typeParameter[0], sectionName);
+
+ const method = {
+ isConstructor,
+ isStatic,
+ name: signature.name,
+ comment: hasComment ? signature.comment.shortText : undefined,
+ returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined,
+ source: {
+ fileName: source.fileName,
+ line: source.line,
+ },
+ callPath,
+ parameters,
+ returnType,
+ typeParameter,
+ };
+ return method;
+ },
+ _convertTypeParameter(entity: TypeDocNode, sectionName: string): TypeParameter {
+ const type = typeDocUtils._convertType(entity.type, sectionName);
+ const parameter = {
+ name: entity.name,
+ type,
+ };
+ return parameter;
+ },
+ _convertParameter(entity: TypeDocNode, sectionName: string): Parameter {
+ let comment = '<No comment>';
+ if (entity.comment && entity.comment.shortText) {
+ comment = entity.comment.shortText;
+ } else if (entity.comment && entity.comment.text) {
+ comment = entity.comment.text;
+ }
+
+ const isOptional = !_.isUndefined(entity.flags.isOptional) ?
+ entity.flags.isOptional :
+ false;
+
+ const type = typeDocUtils._convertType(entity.type, sectionName);
+
+ const parameter = {
+ name: entity.name,
+ comment,
+ isOptional,
+ type,
+ };
+ return parameter;
+ },
+ _convertType(entity: TypeDocType, sectionName: string): Type {
+ const typeArguments = _.map(entity.typeArguments, typeArgument => {
+ return typeDocUtils._convertType(typeArgument, sectionName);
+ });
+ const types = _.map(entity.types, t => {
+ return typeDocUtils._convertType(t, sectionName);
+ });
+
+ const isConstructor = false;
+ const methodIfExists = !_.isUndefined(entity.declaration) ?
+ typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
+ undefined;
+
+ const elementTypeIfExists = !_.isUndefined(entity.elementType) ?
+ {
+ name: entity.elementType.name,
+ typeDocType: entity.elementType.type,
+ } :
+ undefined;
+
+ const type = {
+ name: entity.name,
+ value: entity.value,
+ typeDocType: entity.type,
+ typeArguments,
+ elementType: elementTypeIfExists,
+ types,
+ method: methodIfExists,
+ };
+ return type;
+ },
+};
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
new file mode 100644
index 000000000..eb4c5be3a
--- /dev/null
+++ b/packages/website/ts/utils/utils.ts
@@ -0,0 +1,215 @@
+import * as _ from 'lodash';
+import {
+ SideToAssetToken,
+ SignatureData,
+ Order,
+ Side,
+ TokenByAddress,
+ OrderParty,
+ ScreenWidths,
+ EtherscanLinkSuffixes,
+ Token,
+ Networks,
+} from 'ts/types';
+import * as moment from 'moment';
+import isMobile = require('is-mobile');
+import * as u2f from 'ts/vendor/u2f_api';
+import deepEqual = require('deep-equal');
+import ethUtil = require('ethereumjs-util');
+import BigNumber from 'bignumber.js';
+import {constants} from 'ts/utils/constants';
+
+const LG_MIN_EM = 64;
+const MD_MIN_EM = 52;
+
+export const utils = {
+ assert(condition: boolean, message: string) {
+ if (!condition) {
+ throw new Error(message);
+ }
+ },
+ spawnSwitchErr(name: string, value: any) {
+ return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
+ },
+ isNumeric(n: string) {
+ return !isNaN(parseFloat(n)) && isFinite(Number(n));
+ },
+ // This default unix timestamp is used for orders where the user does not specify an expiry date.
+ // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for
+ // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide
+ // with actual values a user would select.
+ initialOrderExpiryUnixTimestampSec(): BigNumber {
+ const m = moment('2050-01-01');
+ return new BigNumber(m.unix());
+ },
+ convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber {
+ const finalMoment = date;
+ if (!_.isUndefined(time)) {
+ finalMoment.hours(time.hours());
+ finalMoment.minutes(time.minutes());
+ }
+ return new BigNumber(finalMoment.unix());
+ },
+ convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment {
+ return moment.unix(unixTimestampSec.toNumber());
+ },
+ convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string {
+ const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec);
+ const formattedDate: string = m.format('h:MMa MMMM D YYYY');
+ return formattedDate;
+ },
+ generateOrder(networkId: number, exchangeContract: string, sideToAssetToken: SideToAssetToken,
+ orderExpiryTimestamp: BigNumber, orderTakerAddress: string, orderMakerAddress: string,
+ makerFee: BigNumber, takerFee: BigNumber, feeRecipient: string,
+ signatureData: SignatureData, tokenByAddress: TokenByAddress, orderSalt: BigNumber): Order {
+ const makerToken = tokenByAddress[sideToAssetToken[Side.deposit].address];
+ const takerToken = tokenByAddress[sideToAssetToken[Side.receive].address];
+ const order = {
+ maker: {
+ address: orderMakerAddress,
+ token: {
+ name: makerToken.name,
+ symbol: makerToken.symbol,
+ decimals: makerToken.decimals,
+ address: makerToken.address,
+ },
+ amount: sideToAssetToken[Side.deposit].amount.toString(),
+ feeAmount: makerFee.toString(),
+ },
+ taker: {
+ address: orderTakerAddress,
+ token: {
+ name: takerToken.name,
+ symbol: takerToken.symbol,
+ decimals: takerToken.decimals,
+ address: takerToken.address,
+ },
+ amount: sideToAssetToken[Side.receive].amount.toString(),
+ feeAmount: takerFee.toString(),
+ },
+ expiration: orderExpiryTimestamp.toString(),
+ feeRecipient,
+ salt: orderSalt.toString(),
+ signature: signatureData,
+ exchangeContract,
+ networkId,
+ };
+ return order;
+ },
+ consoleLog(message: string) {
+ /* tslint:disable */
+ console.log(message);
+ /* tslint:enable */
+ },
+ sleepAsync(ms: number) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ },
+ deepEqual(actual: any, expected: any, opts?: {strict: boolean}) {
+ return deepEqual(actual, expected, opts);
+ },
+ getColSize(items: number) {
+ const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid
+ const colSize = 12 / items;
+ if (!_.isInteger(colSize)) {
+ throw new Error('Number of cols must be divisible by 12');
+ }
+ return colSize;
+ },
+ getScreenWidth() {
+ const documentEl = document.documentElement;
+ const body = document.getElementsByTagName('body')[0];
+ const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth;
+ const bodyStyles: any = window.getComputedStyle(document.querySelector('body'));
+ const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']);
+
+ // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS
+ // class prefixes. Do not edit these.
+ if (widthInEm > LG_MIN_EM) {
+ return ScreenWidths.LG;
+ } else if (widthInEm > MD_MIN_EM) {
+ return ScreenWidths.MD;
+ } else {
+ return ScreenWidths.SM;
+ }
+ },
+ isUserOnMobile(): boolean {
+ const isUserOnMobile = isMobile();
+ return isUserOnMobile;
+ },
+ getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string {
+ const networkName = constants.networkNameById[networkId];
+ if (_.isUndefined(networkName)) {
+ return undefined;
+ }
+ const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`;
+ return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`;
+ },
+ setUrlHash(anchorId: string) {
+ window.location.hash = anchorId;
+ },
+ async isU2FSupportedAsync(): Promise<boolean> {
+ const w = (window as any);
+ return new Promise((resolve: (isSupported: boolean) => void) => {
+ if (w.u2f && !w.u2f.getApiVersion) {
+ // u2f object was found (Firefox with extension)
+ resolve(true);
+ } else {
+ // u2f object was not found. Using Google polyfill
+ // HACK: u2f.getApiVersion will simply not return a version if the
+ // U2F call fails for any reason. Because of this, we set a hard 3sec
+ // timeout to the request on our end.
+ const getApiVersionTimeoutMs = 3000;
+ const intervalId = setTimeout(() => {
+ resolve(false);
+ }, getApiVersionTimeoutMs);
+ u2f.getApiVersion((version: number) => {
+ clearTimeout(intervalId);
+ resolve(true);
+ });
+ }
+ });
+ },
+ // This checks the error message returned from an injected Web3 instance on the page
+ // after a user was prompted to sign a message or send a transaction and decided to
+ // reject the request.
+ didUserDenyWeb3Request(errMsg: string) {
+ const metamaskDenialErrMsg = 'User denied message';
+ const paritySignerDenialErrMsg = 'Request has been rejected';
+ const ledgerDenialErrMsg = 'Invalid status 6985';
+ const isUserDeniedErrMsg = _.includes(errMsg, metamaskDenialErrMsg) ||
+ _.includes(errMsg, paritySignerDenialErrMsg) ||
+ _.includes(errMsg, ledgerDenialErrMsg);
+ return isUserDeniedErrMsg;
+ },
+ getCurrentEnvironment() {
+ switch (location.host) {
+ case constants.DEVELOPMENT_DOMAIN:
+ return 'development';
+ case constants.STAGING_DOMAIN:
+ return 'staging';
+ case constants.PRODUCTION_DOMAIN:
+ return 'production';
+ default:
+ return 'production';
+ }
+ },
+ getIdFromName(name: string) {
+ const id = name.replace(/ /g, '-');
+ return id;
+ },
+ getAddressBeginAndEnd(address: string): string {
+ const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
+ return truncatedAddress;
+ },
+ hasUniqueNameAndSymbol(tokens: Token[], token: Token) {
+ if (token.isRegistered) {
+ return true; // Since it's registered, it is the canonical token
+ }
+ const registeredTokens = _.filter(tokens, t => t.isRegistered);
+ const tokenWithSameNameIfExists = _.find(registeredTokens, {name: token.name});
+ const isUniqueName = _.isUndefined(tokenWithSameNameIfExists);
+ const tokenWithSameSymbolIfExists = _.find(registeredTokens, {name: token.symbol});
+ const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists);
+ return isUniqueName && isUniqueSymbol;
+ },
+};
diff --git a/packages/website/ts/vendor/u2f_api.js b/packages/website/ts/vendor/u2f_api.js
new file mode 100644
index 000000000..3b538d817
--- /dev/null
+++ b/packages/website/ts/vendor/u2f_api.js
@@ -0,0 +1,760 @@
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+'use strict';
+
+
+/**
+ * Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * Require integration
+ */
+if (typeof module != "undefined") {
+ module.exports = u2f;
+}
+
+/**
+ * FIDO U2F Javascript API Version
+ * @number
+ */
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+ 'U2F_REGISTER_REQUEST': 'u2f_register_request',
+ 'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+ 'U2F_SIGN_REQUEST': 'u2f_sign_request',
+ 'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+ 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+ 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
+};
+
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+ 'OK': 0,
+ 'OTHER_ERROR': 1,
+ 'BAD_REQUEST': 2,
+ 'CONFIGURATION_UNSUPPORTED': 3,
+ 'DEVICE_INELIGIBLE': 4,
+ 'TIMEOUT': 5
+};
+
+
+/**
+ * A message for registration requests
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * appId: ?string,
+ * timeoutSeconds: ?number,
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fRequest;
+
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fResponse;
+
+
+/**
+ * An error object for responses
+ * @typedef {{
+ * errorCode: u2f.ErrorCodes,
+ * errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ * version: string,
+ * challenge: string,
+ * keyHandle: string,
+ * appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ * keyHandle: string,
+ * signatureData: string,
+ * clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ * version: string,
+ * challenge: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: Transports,
+ * appId: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: ?Transports,
+ * appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ * js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+ if (typeof chrome != 'undefined' && chrome.runtime) {
+ // The actual message here does not matter, but we need to get a reply
+ // for the callback to run. Thus, send an empty signature request
+ // in order to get a failure response.
+ var msg = {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: []
+ };
+ chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+ if (!chrome.runtime.lastError) {
+ // We are on a whitelisted origin and can talk directly
+ // with the extension.
+ u2f.getChromeRuntimePort_(callback);
+ } else {
+ // chrome.runtime was available, but we couldn't message
+ // the extension directly, use iframe
+ u2f.getIframePort_(callback);
+ }
+ });
+ } else if (u2f.isAndroidChrome_()) {
+ u2f.getAuthenticatorPort_(callback);
+ } else if (u2f.isIosChrome_()) {
+ u2f.getIosPort_(callback);
+ } else {
+ // chrome.runtime was not available at all, which is normal
+ // when this origin doesn't have access to any extensions.
+ u2f.getIframePort_(callback);
+ }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+ var userAgent = navigator.userAgent;
+ return userAgent.indexOf('Chrome') != -1 &&
+ userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+ return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+ var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+ {'includeTlsChannelId': true});
+ setTimeout(function() {
+ callback(new u2f.WrappedChromeRuntimePort_(port));
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedAuthenticatorPort_());
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedIosPort_());
+ }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+ this.port_ = port;
+};
+
+/**
+ * Format and return a sign request compliant with the JS API version supported by the extension.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatSignRequest_ =
+ function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: challenge,
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: signRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ appId: appId,
+ challenge: challenge,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+};
+
+/**
+ * Format and return a register request compliant with the JS API version supported by the extension..
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatRegisterRequest_ =
+ function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ for (var i = 0; i < registerRequests.length; i++) {
+ registerRequests[i].appId = appId;
+ }
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: registerRequests[0],
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ signRequests: signRequests,
+ registerRequests: registerRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ appId: appId,
+ registerRequests: registerRequests,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+};
+
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+ this.port_.postMessage(message);
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+ function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message' || name == 'onmessage') {
+ this.port_.onMessage.addListener(function(message) {
+ // Emulate a minimal MessageEvent object
+ handler({'data': message});
+ });
+ } else {
+ console.error('WrappedChromeRuntimePort only supports onMessage');
+ }
+};
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+ this.requestId_ = -1;
+ this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+ var intentUrl =
+ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+ ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+ ';end';
+ document.location = intentUrl;
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+ return "WrappedAuthenticatorPort_";
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message') {
+ var self = this;
+ /* Register a callback to that executes when
+ * chrome injects the response. */
+ window.addEventListener(
+ 'message', self.onRequestUpdate_.bind(self, handler), false);
+ } else {
+ console.error('WrappedAuthenticatorPort only supports message');
+ }
+};
+
+/**
+ * Callback invoked when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+ function(callback, message) {
+ var messageObject = JSON.parse(message.data);
+ var intentUrl = messageObject['intentURL'];
+
+ var errorCode = messageObject['errorCode'];
+ var responseObject = null;
+ if (messageObject.hasOwnProperty('data')) {
+ responseObject = /** @type {Object} */ (
+ JSON.parse(messageObject['data']));
+ }
+
+ callback({'data': responseObject});
+};
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+/*
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+ 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+*/
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+ 'intent:#Intent;action=com.ledger.android.u2f.bridge.AUTHENTICATE';
+
+
+/**
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+ var str = JSON.stringify(message);
+ var url = "u2f://auth?" + encodeURI(str);
+ location.replace(url);
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+ return "WrappedIosPort_";
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name !== 'message') {
+ console.error('WrappedIosPort only supports message');
+ }
+};
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+ // Create the iframe
+ var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+ var iframe = document.createElement('iframe');
+ iframe.src = iframeOrigin + '/u2f-comms.html';
+ iframe.setAttribute('style', 'display:none');
+ document.body.appendChild(iframe);
+
+ var channel = new MessageChannel();
+ var ready = function(message) {
+ if (message.data == 'ready') {
+ channel.port1.removeEventListener('message', ready);
+ callback(channel.port1);
+ } else {
+ console.error('First event on iframe port was not "ready"');
+ }
+ };
+ channel.port1.addEventListener('message', ready);
+ channel.port1.start();
+
+ iframe.addEventListener('load', function() {
+ // Deliver the port to the iframe and initialize
+ iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+ });
+};
+
+
+//High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ * |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+ if (u2f.port_) {
+ callback(u2f.port_);
+ } else {
+ if (u2f.waitingForPort_.length == 0) {
+ u2f.getMessagePort(function(port) {
+ u2f.port_ = port;
+ u2f.port_.addEventListener('message',
+ /** @type {function(Event)} */ (u2f.responseHandler_));
+
+ // Careful, here be async callbacks. Maybe.
+ while (u2f.waitingForPort_.length)
+ u2f.waitingForPort_.shift()(u2f.port_);
+ });
+ }
+ u2f.waitingForPort_.push(callback);
+ }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+ var response = message.data;
+ var reqId = response['requestId'];
+ if (!reqId || !u2f.callbackMap_[reqId]) {
+ console.error('Unknown or missing requestId in response.');
+ return;
+ }
+ var cb = u2f.callbackMap_[reqId];
+ delete u2f.callbackMap_[reqId];
+ cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual sign request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+ //console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual sign request in the supported API version.
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual register request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+ //console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual register request in the supported API version.
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatRegisterRequest_(
+ appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ // If we are using Android Google Authenticator or iOS client app,
+ // do not fire an intent to ask which JS API version to use.
+ if (port.getPortType) {
+ var apiVersion;
+ switch (port.getPortType()) {
+ case 'WrappedIosPort_':
+ case 'WrappedAuthenticatorPort_':
+ apiVersion = 1.1;
+ break;
+
+ default:
+ apiVersion = 0;
+ break;
+ }
+ callback({ 'js_api_version': apiVersion });
+ return;
+ }
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var req = {
+ type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+ timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+ requestId: reqId
+ };
+ port.postMessage(req);
+ });
+};
diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts
new file mode 100644
index 000000000..24279f5d2
--- /dev/null
+++ b/packages/website/ts/web3_wrapper.ts
@@ -0,0 +1,146 @@
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import BigNumber from 'bignumber.js';
+import promisify = require('es6-promisify');
+import {Dispatcher} from 'ts/redux/dispatcher';
+
+export class Web3Wrapper {
+ private dispatcher: Dispatcher;
+ private web3: Web3;
+ private prevNetworkId: number;
+ private shouldPollUserAddress: boolean;
+ private watchNetworkAndBalanceIntervalId: number;
+ private prevUserEtherBalanceInEth: BigNumber;
+ private prevUserAddress: string;
+ constructor(dispatcher: Dispatcher, provider: Web3.Provider, networkIdIfExists: number,
+ shouldPollUserAddress: boolean) {
+ this.dispatcher = dispatcher;
+ this.prevNetworkId = networkIdIfExists;
+ this.shouldPollUserAddress = shouldPollUserAddress;
+
+ this.web3 = new Web3();
+ this.web3.setProvider(provider);
+
+ this.startEmittingNetworkConnectionAndUserBalanceStateAsync();
+ }
+ public isAddress(address: string) {
+ return this.web3.isAddress(address);
+ }
+ public async getAccountsAsync(): Promise<string[]> {
+ const addresses = await promisify(this.web3.eth.getAccounts)();
+ return addresses;
+ }
+ public async getFirstAccountIfExistsAsync() {
+ const addresses = await this.getAccountsAsync();
+ if (_.isEmpty(addresses)) {
+ return '';
+ }
+ return (addresses as string[])[0];
+ }
+ public async getNodeVersionAsync() {
+ const nodeVersion = await promisify(this.web3.version.getNode)();
+ return nodeVersion;
+ }
+ public getProviderObj() {
+ return this.web3.currentProvider;
+ }
+ public async getNetworkIdIfExists() {
+ try {
+ const networkId = await this.getNetworkAsync();
+ return Number(networkId);
+ } catch (err) {
+ return undefined;
+ }
+ }
+ public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
+ const balanceInWei: BigNumber = await promisify(this.web3.eth.getBalance)(owner);
+ const balanceEthOldBigNumber = this.web3.fromWei(balanceInWei, 'ether');
+ const balanceEth = new BigNumber(balanceEthOldBigNumber);
+ return balanceEth;
+ }
+ public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
+ const code = await promisify(this.web3.eth.getCode)(address);
+ // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients
+ const zeroHexAddressRegex = /^0[xX][0]*$/;
+ const didFindCode = _.isNull(code.match(zeroHexAddressRegex));
+ return didFindCode;
+ }
+ public async signTransactionAsync(address: string, message: string): Promise<string> {
+ const signData = await promisify(this.web3.eth.sign)(address, message);
+ return signData;
+ }
+ public async getBlockTimestampAsync(blockHash: string): Promise<number> {
+ const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash);
+ return timestamp;
+ }
+ public destroy() {
+ this.stopEmittingNetworkConnectionAndUserBalanceStateAsync();
+ // HACK: stop() is only available on providerEngine instances
+ const provider = this.web3.currentProvider;
+ if (!_.isUndefined((provider as any).stop)) {
+ (provider as any).stop();
+ }
+ }
+ // This should only be called from the LedgerConfigDialog
+ public updatePrevUserAddress(userAddress: string) {
+ this.prevUserAddress = userAddress;
+ }
+ private async getNetworkAsync() {
+ const networkId = await promisify(this.web3.version.getNetwork)();
+ return networkId;
+ }
+ private async startEmittingNetworkConnectionAndUserBalanceStateAsync() {
+ if (!_.isUndefined(this.watchNetworkAndBalanceIntervalId)) {
+ return; // we are already emitting the state
+ }
+
+ let prevNodeVersion: string;
+ this.prevUserEtherBalanceInEth = new BigNumber(0);
+ this.dispatcher.updateNetworkId(this.prevNetworkId);
+ this.watchNetworkAndBalanceIntervalId = window.setInterval(async () => {
+ // Check for network state changes
+ const currentNetworkId = await this.getNetworkIdIfExists();
+ if (currentNetworkId !== this.prevNetworkId) {
+ this.prevNetworkId = currentNetworkId;
+ this.dispatcher.updateNetworkId(currentNetworkId);
+ }
+
+ // Check for node version changes
+ const currentNodeVersion = await this.getNodeVersionAsync();
+ if (currentNodeVersion !== prevNodeVersion) {
+ prevNodeVersion = currentNodeVersion;
+ this.dispatcher.updateNodeVersion(currentNodeVersion);
+ }
+
+ if (this.shouldPollUserAddress) {
+ const userAddressIfExists = await this.getFirstAccountIfExistsAsync();
+ // Update makerAddress on network change
+ if (this.prevUserAddress !== userAddressIfExists) {
+ this.prevUserAddress = userAddressIfExists;
+ this.dispatcher.updateUserAddress(userAddressIfExists);
+ }
+
+ // Check for user ether balance changes
+ if (userAddressIfExists !== '') {
+ await this.updateUserEtherBalanceAsync(userAddressIfExists);
+ }
+ } else {
+ // This logic is primarily for the Ledger, since we don't regularly poll for the address
+ // we simply update the balance for the last fetched address.
+ if (!_.isEmpty(this.prevUserAddress)) {
+ await this.updateUserEtherBalanceAsync(this.prevUserAddress);
+ }
+ }
+ }, 5000);
+ }
+ private async updateUserEtherBalanceAsync(userAddress: string) {
+ const balance = await this.getBalanceInEthAsync(userAddress);
+ if (!balance.eq(this.prevUserEtherBalanceInEth)) {
+ this.prevUserEtherBalanceInEth = balance;
+ this.dispatcher.updateUserEtherBalance(balance);
+ }
+ }
+ private stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
+ clearInterval(this.watchNetworkAndBalanceIntervalId);
+ }
+}
diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json
new file mode 100644
index 000000000..15e764c02
--- /dev/null
+++ b/packages/website/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true,
+ "outDir": "./transpiled/",
+ "sourceMap": true,
+ "noImplicitAny": true,
+ "module": "commonjs",
+ "target": "es5",
+ "jsx": "react",
+ "baseUrl": "./",
+ "allowJs": true,
+ "paths": {
+ "*": [ "node_modules/@types/*", "*"]
+ }
+ },
+ "include": [
+ "./ts/**/*",
+ "../../node_modules/web3-typescript-typings/index.d.ts"
+ ]
+}
diff --git a/packages/website/tslint.json b/packages/website/tslint.json
new file mode 100644
index 000000000..6ac3ed760
--- /dev/null
+++ b/packages/website/tslint.json
@@ -0,0 +1,9 @@
+{
+ "extends": [
+ "@0xproject/tslint-config"
+ ],
+ "rules": {
+ "no-implicit-dependencies": false,
+ "no-object-literal-type-assertion": false
+ }
+}
diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js
new file mode 100644
index 000000000..c436888bd
--- /dev/null
+++ b/packages/website/webpack.config.js
@@ -0,0 +1,86 @@
+const path = require('path');
+const webpack = require('webpack');
+
+module.exports = {
+ entry: ['./ts/index.tsx'],
+ output: {
+ path: path.join(__dirname, '/public'),
+ filename: 'bundle.js',
+ chunkFilename: 'bundle-[name].js',
+ publicPath: '/',
+ },
+ devtool: 'source-map',
+ resolve: {
+ modules: [
+ path.join(__dirname, '/ts'),
+ 'node_modules',
+ ],
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md'],
+ alias: {
+ ts: path.join(__dirname, '/ts'),
+ less: path.join(__dirname, '/less'),
+ md: path.join(__dirname, '/md'),
+ },
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ loader: 'source-map-loader',
+ },
+ {
+ test: /\.tsx?$/,
+ loader: 'awesome-typescript-loader',
+ },
+ {
+ test: /\.md$/,
+ use: 'raw-loader',
+ },
+ {
+ test: /\.less$/,
+ loader: 'style-loader!css-loader!less-loader',
+ exclude: /node_modules/,
+ },
+ {
+ test: /\.css$/,
+ loaders: ['style-loader', 'css-loader'],
+ },
+ {
+ test: /\.json$/,
+ loader: 'json-loader',
+ },
+ ],
+ },
+ devServer: {
+ port: 3572,
+ historyApiFallback: {
+ // Fixes issue where having dots in URL path that aren't part of fileNames causes webpack-dev-server
+ // to fail. Doc versions have dots in them, therefore we special case these urls to also load index.html.
+ // Source: https://github.com/cvut/fittable/issues/171
+ rewrites: [
+ {
+ from: /^\/docs\/.*$/,
+ to: function() {
+ return 'index.html';
+ }
+ }
+ ]
+ },
+ disableHostCheck: true,
+ },
+ plugins: process.env.NODE_ENV === 'production' ? [
+ // Since we do not use moment's locale feature, we exclude them from the bundle.
+ // This reduces the bundle size by 0.4MB.
+ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
+ }
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ mangle: {
+ except: ['BigNumber']
+ }
+ })
+ ] : [],
+};