├── .gitignore ├── LICENSE ├── README.md ├── assets ├── crx │ ├── css │ │ ├── reset.css │ │ └── style.css │ ├── html │ │ └── embed.html │ ├── img │ │ └── icon.png │ ├── js │ │ ├── background.js │ │ └── embed.js │ └── manifest.json └── icons │ ├── fallback │ ├── A.png │ ├── B.png │ ├── C.png │ ├── D.png │ ├── E.png │ ├── F.png │ ├── G.png │ ├── H.png │ ├── I.png │ ├── J.png │ ├── K.png │ ├── L.png │ ├── M.png │ ├── N.png │ ├── O.png │ ├── P.png │ ├── Q.png │ ├── R.png │ ├── S.png │ ├── T.png │ ├── U.png │ ├── V.png │ ├── W.png │ ├── X.png │ ├── Y.png │ └── Z.png │ ├── keep.google.com.png │ ├── messenger.com.png │ ├── web.whatsapp.com.png │ └── youtube.com.png ├── lib ├── middleware │ ├── error.js │ └── redirect.js └── routes │ ├── applicationize.js │ └── now.js ├── package-lock.json ├── package.json ├── public ├── css │ ├── bootstrap-colorpicker.min.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── custom.css │ ├── flexslider.css │ ├── less │ │ └── theme.less │ ├── lightbox.min.css │ ├── theme-red.css │ ├── theme.css │ └── themify-icons.css ├── fonts │ ├── themify.eot │ ├── themify.svg │ ├── themify.ttf │ └── themify.woff ├── img │ ├── bootstrap-colorpicker │ │ ├── alpha-horizontal.png │ │ ├── alpha.png │ │ ├── hue-horizontal.png │ │ ├── hue.png │ │ └── saturation.png │ ├── cover.jpg │ ├── favicon.png │ ├── fork.png │ ├── lightbox │ │ ├── close.png │ │ ├── loading.gif │ │ ├── next.png │ │ └── prev.png │ ├── logo-dark.png │ ├── logo-light.png │ ├── preview.png │ └── screenshots │ │ └── install.png ├── index.html ├── js │ ├── applicationizer.js │ ├── bootstrap-colorpicker.min.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── flexslider.min.js │ ├── jquery.min.js │ ├── lightbox.min.js │ ├── masonry.min.js │ ├── parallax.js │ ├── scripts.js │ └── smooth-scroll.min.js └── now.html └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | DEPLOY 2 | .vscode/ 3 | .idea/ 4 | .elasticbeanstalk/ 5 | node_modules/ 6 | typings/ 7 | staging/ 8 | ssl/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Applicationize 2 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eladnava/applicationize?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 3 | 4 | > **Note:** Applicationize is no longer supported. Google Chrome has officially stopped running Packaged Apps, rendering Applicationize unusable. Google Chrome now comes with a built-in feature called Application Shortcuts. It basically makes it possible to convert any web page (or web app) into a desktop app by clicking the hamburger icon in the top right -> More Tools -> "Create Shortcut" and ticking "Open as window". It is recommended to migrate to using this built-in feature, or consider an alternative solution such as [nativefier](https://github.com/nativefier/nativefier). 5 | 6 | Applicationize converts your favorite web apps into desktop apps with their own dedicated launcher icon. 7 | 8 | It generates a Google Chrome extension that embeds your favorite [SPA](https://en.wikipedia.org/wiki/Single-page_application) web app and places a custom shortcut icon in your app launcher when you install it. 9 | 10 | https://applicationize.me/ 11 | 12 | ## Demo 13 | 14 | Here's a screenshot of Facebook Messenger (messenger.com) running as an applicationized desktop app: 15 | 16 |
17 | 18 | 19 | ## How to Use 20 | 21 | 1. Go to https://applicationize.me/now 22 | 2. Enter a URL to any web app, such as https://web.whatsapp.com/ 23 | 3. Press `Enter` and download the generated Chrome extension 24 | 4. Open a new tab and navigate to `chrome://extensions/` 25 | 5. Drag the downloaded `.crx` file from its download folder to the extensions page to install it 26 | 27 | That's it! Your applicationized web app is now available via your app launcher. We recommend pinning it to your application dock or system taskbar! 28 | 29 | ## Keyboard Shortcuts 30 | 31 | * CTRL / CMD + [ to go back 32 | * CTRL / CMD + ] to go forward 33 | * CTRL / CMD + + to zoom in 34 | * CTRL / CMD + - to zoom out 35 | * CTRL / CMD + 0 to reset zoom 36 | * CTRL / CMD + P to print page 37 | * CTRL / CMD + F to find in page 38 | * CTRL / CMD + R to refresh the page 39 | * CTRL / CMD + L to copy current page URL 40 | * CTRL + CMD + F to enter full screen mode (ALT + CTRL + F on Windows) 41 | 42 | ## Contributing 43 | 44 | * If you find a bug or wish to make some kind of change, please create an issue first 45 | * Make sure your code conventions are in-line with the project's code style 46 | * Make your commits and PRs as tiny as possible - one feature or bugfix at a time 47 | * Write detailed commit messages, in-line with the project's commit naming conventions 48 | 49 | ## Running the Server 50 | 51 | Run the following commands in the root directory of this project: 52 | 53 | ```shell 54 | npm install 55 | npm start 56 | ``` 57 | 58 | Then, visit [http://localhost:8080/](http://localhost:8080/) to browse to your local instance of Applicationize. 59 | 60 | ## Contributors 61 | 62 | Thanks to the following individuals and organizations who help pay for hosting & domain renewal: 63 | 64 | * [@guidokok](https://github.com/guidokok) - Guido Bedarida 65 | * [Seam.es](https://seam.es) - we design & build for the web; stitch by stitch 66 | * [Fan Studio UK](https://www.fanstudio.co.uk/) - we specialize in mobile game app development and gamification strategies for startups and enterprise clients 67 | 68 | 69 | 70 | ## License 71 | 72 | Apache 2.0 73 | -------------------------------------------------------------------------------- /assets/crx/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /assets/crx/css/style.css: -------------------------------------------------------------------------------- 1 | webview { 2 | width: 100vw; 3 | height: 100vh; 4 | } 5 | 6 | #find-box { 7 | top: 0; 8 | right: 70px; 9 | width: 250px; 10 | display: none; 11 | position: absolute; 12 | background: #f1f1f1; 13 | border: 1px solid #eee; 14 | border-radius: 0 0 3px 3px; 15 | } 16 | 17 | #find-text { 18 | width: 92%; 19 | margin: 4px; 20 | padding: 5px; 21 | background: #fff; 22 | border-radius: 3px; 23 | border: 1px solid #e6e6e6; 24 | } 25 | 26 | #dialog-box { 27 | top: 0; 28 | width: 360px; 29 | left: -180px; 30 | display: none; 31 | position: absolute; 32 | background: #fff; 33 | border-radius: 3px; 34 | margin: 30px 0 0 50%; 35 | box-shadow: 1px 5px 15px 0px rgba(0, 0, 0, 0.34); 36 | } 37 | 38 | #dialog-box-text { 39 | padding: 20px; 40 | font-size: 13px; 41 | font-family: Arial, Helvetica, sans-serif; 42 | } 43 | 44 | #dialog-box-input { 45 | padding: 5px; 46 | display: none; 47 | margin: 0 20px 20px 20px; 48 | width: calc(100% - 45px); 49 | } 50 | 51 | .dialog-box-button { 52 | bottom: 0; 53 | width: 70px; 54 | height: 27px; 55 | float: right; 56 | color: #444; 57 | font-size: 12px; 58 | position: relative; 59 | margin: 0 11px 20px 0; 60 | border-radius: 2px; 61 | border: 1px solid rgba(0, 0, 0, 0.25); 62 | text-shadow: 0 1px 0 rgb(240, 240, 240); 63 | background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede); 64 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.75); 65 | } -------------------------------------------------------------------------------- /assets/crx/html/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Applicationize 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
Example dialog text
21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /assets/crx/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/crx/img/icon.png -------------------------------------------------------------------------------- /assets/crx/js/background.js: -------------------------------------------------------------------------------- 1 | // This will be injected dynamically at extension creation time 2 | var appConfig = {inject-background-script-config}; 3 | 4 | /** 5 | * Listens for the app launching then creates the window 6 | * 7 | * @see http://developer.chrome.com/apps/app.runtime.html 8 | * @see http://developer.chrome.com/apps/app.window.html 9 | */ 10 | chrome.app.runtime.onLaunched.addListener(function () { 11 | runApp(); 12 | }); 13 | 14 | /** 15 | * Listens for the app restarting then re-creates the window. 16 | * 17 | * @see http://developer.chrome.com/apps/app.runtime.html 18 | */ 19 | chrome.app.runtime.onRestarted.addListener(function () { 20 | runApp(); 21 | }); 22 | 23 | /** 24 | * Creates the window for the application. 25 | * 26 | * @see http://developer.chrome.com/apps/app.window.html 27 | */ 28 | function runApp() { 29 | // Creat a new Chrome app window 30 | chrome.app.window.create('html/embed.html', appConfig.chromeAppWindow, onWindowLoaded()); 31 | } 32 | 33 | /** 34 | * Called before the contentWindow's onload event 35 | * 36 | * @see http://developer.chrome.com/apps/app.window.html 37 | */ 38 | function onWindowLoaded(popup) { 39 | return function (win) { 40 | // On window loaded event 41 | win.contentWindow.onload = function () { 42 | // Get webview 43 | var webview = win.contentWindow.document.getElementById('webview'); 44 | 45 | // Override default user agent if provided 46 | if (appConfig.userAgent) { 47 | webview.setUserAgentOverride(appConfig.userAgent); 48 | } 49 | 50 | // Sign up for 'permissionrequest' event 51 | webview.addEventListener('permissionrequest', function (e) { 52 | // Allow all permission requests 53 | e.request.allow(); 54 | }); 55 | 56 | // Sign up for 'newwindow' event 57 | // Emitted when a target='_blank' link is clicked within the webview 58 | webview.addEventListener('newwindow', function (e) { 59 | // Parse target window URL to extract hostname 60 | var parsedUrl = document.createElement('a'); 61 | parsedUrl.href = e.targetUrl; 62 | 63 | // Popup? 64 | if (e.initialWidth > 0 && e.initialHeight > 0) { 65 | // Open it in a popup window with a set width and height 66 | return chrome.app.window.create('html/embed.html', { frame: { type: 'chrome' }, innerBounds: { width: e.initialWidth, height: e.initialHeight } }, onWindowLoaded(e)); 67 | } 68 | // Open app links internally? 69 | else if (appConfig.behavior.internalLinks && parsedUrl.hostname.includes(appConfig.hostname)) { 70 | return chrome.app.window.create('html/embed.html', { frame: { type: 'chrome' }, innerBounds: appConfig.chromeAppWindow.innerBounds }, onWindowLoaded(e)); 71 | } 72 | 73 | // Open the link in a new browser tab/window 74 | win.contentWindow.open(e.targetUrl); 75 | }); 76 | 77 | // Is this a popup window? 78 | if (popup) { 79 | // Override webview source with popup's target URL 80 | webview.src = popup.targetUrl; 81 | 82 | // Attach original calling window to popup webview (accessible via window.opener) 83 | popup.window.attach(webview); 84 | } 85 | }; 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /assets/crx/js/embed.js: -------------------------------------------------------------------------------- 1 | // Get webview element 2 | var webview = document.getElementById('webview'); 3 | 4 | // Get find box elements 5 | var findBox = document.getElementById('find-box'); 6 | var findInput = document.getElementById('find-text'); 7 | 8 | // Get dialog box elements 9 | var dialogBox = document.getElementById("dialog-box"); 10 | var dialogOk = document.getElementById("dialog-box-ok"); 11 | var dialogText = document.getElementById("dialog-box-text"); 12 | var dialogInput = document.getElementById("dialog-box-input"); 13 | var dialogCancel = document.getElementById("dialog-box-cancel"); 14 | 15 | // Initial page zoom factor 16 | var zoomFactor = 1.0; 17 | 18 | // Listen to keydown event 19 | window.addEventListener('keydown', function (e) { 20 | // Check whether CTRL on Windows or CMD on Mac is pressed 21 | var modifierActive = (navigator.platform.startsWith('Mac')) ? e.metaKey : e.ctrlKey; 22 | var altModifierActive = (navigator.platform.startsWith('Mac')) ? e.ctrlKey : e.altKey; 23 | 24 | // Enter full screen mode (CMD/ALT + CTRL + F) 25 | if (modifierActive && altModifierActive && e.keyCode == 'F'.charCodeAt(0)) { 26 | // Get current focused window 27 | var window = chrome.app.window.current(); 28 | 29 | // Check if currently full screen 30 | if (!window.isFullscreen()) { 31 | // Enter full screen mode 32 | window.fullscreen(); 33 | } 34 | else { 35 | // Exit full screen mode 36 | window.restore(); 37 | } 38 | 39 | // Prevent other shortcut checks 40 | return; 41 | } 42 | 43 | // Refresh the page (CTRL/CMD + R) 44 | if (modifierActive && e.keyCode == 'R'.charCodeAt(0)) { 45 | webview.reload(); 46 | } 47 | 48 | // Find in page (CTRL/CMD + F) 49 | if (modifierActive && e.keyCode == 'F'.charCodeAt(0)) { 50 | // Show the find box 51 | findBox.style.display = 'block'; 52 | 53 | // Focus the find input 54 | findInput.focus(); 55 | 56 | // Select all existing text (if any) 57 | findInput.select(); 58 | } 59 | 60 | // Copy URL (CTRL/CMD + L) 61 | if (modifierActive && e.keyCode == 'L'.charCodeAt(0)) { 62 | // Copy webview source to clipboard 63 | copyToClipboard(webview.src, 'text/plain'); 64 | } 65 | 66 | // Print (CTRL/CMD + P) 67 | if (modifierActive && e.keyCode == 'P'.charCodeAt(0)) { 68 | webview.print(); 69 | } 70 | 71 | // Zoom in (CTRL/CMD +) 72 | if (modifierActive && e.keyCode == 187) { 73 | zoomFactor += 0.1; 74 | webview.setZoom(zoomFactor); 75 | } 76 | 77 | // Zoom out (CTRL/CMD -) 78 | if (modifierActive && e.keyCode == 189) { 79 | zoomFactor -= 0.1; 80 | 81 | // Don't let zoom drop below 0.2 82 | if (zoomFactor <= 0.2) { 83 | zoomFactor = 0.2; 84 | } 85 | 86 | webview.setZoom(zoomFactor); 87 | } 88 | 89 | // Reset zoom (CTRL/CMD + 0) 90 | if (modifierActive && e.keyCode == '0'.charCodeAt(0)) { 91 | zoomFactor = 1.0; 92 | webview.setZoom(zoomFactor); 93 | } 94 | }); 95 | 96 | // Listen for webview load event 97 | webview.addEventListener('contentload', function () { 98 | // Execute JS script within webview 99 | webview.executeScript({ 100 | // Send a Chrome runtime message every time the keydown event is fired within webview 101 | code: `window.addEventListener('keydown', function (e) { 102 | chrome.runtime.sendMessage({ event: 'keydown', params: { ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, keyCode: e.keyCode } }); 103 | });`}); 104 | }); 105 | 106 | // Listen for Chrome runtime messages 107 | chrome.runtime.onMessage.addListener( 108 | function (request, sender, sendResponse) { 109 | // Check for keydown event 110 | if (request.event === 'keydown') { 111 | // Invoke the local window's keydown event handler 112 | window.dispatchEvent(new KeyboardEvent('keydown',request.params)); 113 | } 114 | } 115 | ); 116 | 117 | // Find input: listen to keydown event 118 | findInput.addEventListener('keyup', function (e) { 119 | // Search for current input text 120 | webview.find(findInput.value, { matchCase: false }); 121 | 122 | // Escape key 123 | if (e.keyCode === 27) { 124 | webview.stopFinding(); 125 | findBox.style.display = 'none'; 126 | } 127 | }); 128 | 129 | function copyToClipboard(str, mimetype) { 130 | // Listen for 'oncopy' event 131 | document.oncopy = function (event) { 132 | event.clipboardData.setData(mimetype, str); 133 | event.preventDefault(); 134 | }; 135 | 136 | // Execute browser command 'Copy' 137 | document.execCommand("Copy", false, null); 138 | } 139 | 140 | // Custom alert dialog popup 141 | var dialogController; 142 | 143 | // Listen for dialog cancellation button click 144 | dialogCancel.addEventListener('click', function () { 145 | // Send cancellation 146 | dialogController.cancel(); 147 | 148 | // Hide dialog box 149 | dialogBox.style.display = 'none'; 150 | }); 151 | 152 | // Listen for dialog OK button click 153 | dialogOk.addEventListener('click', function () { 154 | // Send OK value 155 | dialogController.ok(dialogInput.value); 156 | 157 | // Hide dialog box 158 | dialogBox.style.display = 'none'; 159 | }); 160 | 161 | // Listen for dialog event on webview for new alert dialogs 162 | webview.addEventListener('dialog', function (e) { 163 | // Prevent default logic 164 | e.preventDefault(); 165 | 166 | // Extract dialog type and text 167 | var text = e.messageText; 168 | var dialogType = e.messageType; 169 | 170 | // Keep a reference to dialog object 171 | dialogController = e.dialog; 172 | 173 | // Set dialog text 174 | dialogText.innerHTML = text; 175 | 176 | // Reset dialog input 177 | dialogInput.value = ''; 178 | 179 | // Hide it by default 180 | dialogInput.style.display = 'none'; 181 | 182 | // Alert? 183 | if (dialogType == 'alert') { 184 | // Hide cancel button 185 | dialogCancel.style.display = 'none'; 186 | } 187 | else { 188 | // Another type of dialog, show cancel button 189 | dialogCancel.style.display = 'block'; 190 | 191 | // Prompt? 192 | if (dialogType == 'prompt') { 193 | // Show text input field 194 | dialogInput.style.display = 'block'; 195 | } 196 | } 197 | 198 | // Show dialog box 199 | dialogBox.style.display = 'block'; 200 | }); -------------------------------------------------------------------------------- /assets/crx/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Applicationize", 4 | "description": "A shortcut app generated via Applicationize.", 5 | "version": "1.0", 6 | "icons": { 7 | "128": "img/icon.png" 8 | }, 9 | "app": { 10 | "background": { 11 | "scripts": [ 12 | "js/background.js" 13 | ], 14 | "pages": [ 15 | "html/embed.html" 16 | ] 17 | } 18 | }, 19 | "permissions": [ 20 | "webview", 21 | "fullscreen" 22 | ] 23 | } -------------------------------------------------------------------------------- /assets/icons/fallback/A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/A.png -------------------------------------------------------------------------------- /assets/icons/fallback/B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/B.png -------------------------------------------------------------------------------- /assets/icons/fallback/C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/C.png -------------------------------------------------------------------------------- /assets/icons/fallback/D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/D.png -------------------------------------------------------------------------------- /assets/icons/fallback/E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/E.png -------------------------------------------------------------------------------- /assets/icons/fallback/F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/F.png -------------------------------------------------------------------------------- /assets/icons/fallback/G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/G.png -------------------------------------------------------------------------------- /assets/icons/fallback/H.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/H.png -------------------------------------------------------------------------------- /assets/icons/fallback/I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/I.png -------------------------------------------------------------------------------- /assets/icons/fallback/J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/J.png -------------------------------------------------------------------------------- /assets/icons/fallback/K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/K.png -------------------------------------------------------------------------------- /assets/icons/fallback/L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/L.png -------------------------------------------------------------------------------- /assets/icons/fallback/M.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/M.png -------------------------------------------------------------------------------- /assets/icons/fallback/N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/N.png -------------------------------------------------------------------------------- /assets/icons/fallback/O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/O.png -------------------------------------------------------------------------------- /assets/icons/fallback/P.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/P.png -------------------------------------------------------------------------------- /assets/icons/fallback/Q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/Q.png -------------------------------------------------------------------------------- /assets/icons/fallback/R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/R.png -------------------------------------------------------------------------------- /assets/icons/fallback/S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/S.png -------------------------------------------------------------------------------- /assets/icons/fallback/T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/T.png -------------------------------------------------------------------------------- /assets/icons/fallback/U.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/U.png -------------------------------------------------------------------------------- /assets/icons/fallback/V.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/V.png -------------------------------------------------------------------------------- /assets/icons/fallback/W.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/W.png -------------------------------------------------------------------------------- /assets/icons/fallback/X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/X.png -------------------------------------------------------------------------------- /assets/icons/fallback/Y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/Y.png -------------------------------------------------------------------------------- /assets/icons/fallback/Z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/fallback/Z.png -------------------------------------------------------------------------------- /assets/icons/keep.google.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/keep.google.com.png -------------------------------------------------------------------------------- /assets/icons/messenger.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/messenger.com.png -------------------------------------------------------------------------------- /assets/icons/web.whatsapp.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/web.whatsapp.com.png -------------------------------------------------------------------------------- /assets/icons/youtube.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/assets/icons/youtube.com.png -------------------------------------------------------------------------------- /lib/middleware/error.js: -------------------------------------------------------------------------------- 1 | // Error middleware 2 | module.exports = function error() { 3 | // Return generator function 4 | return function* error(next) { 5 | try { 6 | yield next; 7 | } catch (err) { 8 | // Set response status code 9 | this.status = err.status || 500; 10 | this.body = { error: err.message || 'An unexpected error occurred.' }; 11 | 12 | // Log to console 13 | console.log('Unhandled exception', err); 14 | 15 | // Emit app-wide error 16 | //this.app.emit('error', err, this); 17 | } 18 | }; 19 | }; -------------------------------------------------------------------------------- /lib/middleware/redirect.js: -------------------------------------------------------------------------------- 1 | module.exports = function httpsRedirect() { 2 | // Redirect users to HTTPS endpoint 3 | return function* (next) { 4 | // Didn't request 'https://' endpoint? 5 | if (this.protocol !== 'https' && this.hostname !== 'localhost') { 6 | // Redirect to HTTPS endpoint with same path 7 | return this.redirect('https://' + this.host + this.url); 8 | } 9 | 10 | // Execute downstream middleware 11 | yield next; 12 | }; 13 | }; -------------------------------------------------------------------------------- /lib/routes/applicationize.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var url = require('url'); 3 | var pem = require('pem'); 4 | var path = require('path'); 5 | var zlib = require('zlib'); 6 | var fs = require('fs-extra'); 7 | var stream = require('stream'); 8 | var request = require('request'); 9 | var cheerio = require('cheerio'); 10 | var thunkify = require('thunkify'); 11 | var validator = require('validator'); 12 | 13 | // Convert callbacks to thunks 14 | var requestThunk = thunkify(request); 15 | var createCertificate = thunkify(pem.createCertificate); 16 | 17 | // CRX packaging module, instantiated with the `new` keyword 18 | var Extension = require('crx'); 19 | 20 | // Workaround for #114: os.tmpDir is not a function (on Node 14+) 21 | // https://stackoverflow.com/a/62163963/1123355 22 | if (!os.tmpDir && os.tmpdir) { 23 | os.tmpDir = os.tmpdir; 24 | } 25 | 26 | // POST /generate 27 | module.exports = function* () { 28 | // Build .crx config for the provided URL 29 | var crxConfig = yield buildCrxConfig(this); 30 | 31 | // Generate the .crx file based on the config 32 | var crxBuffer = yield generateCrx(crxConfig); 33 | 34 | // Wrap in a .zip to work around the CRX_REQUIRED_PROOF_MISSING error 35 | var zipBuffer = yield generateZipArchive(crxBuffer); 36 | 37 | // Send zip file to the browser (save to disk) 38 | sendZipFile(this, crxConfig, zipBuffer); 39 | }; 40 | 41 | function* buildCrxConfig(ctx) { 42 | // Get target URL from input 43 | var url = ctx.body.url; 44 | 45 | // Bad input? 46 | if (!url) { 47 | throw { status: 400, message: 'Please provide a URL to continue.' }; 48 | } 49 | 50 | // Parse and validate the input URL (may throw an error) 51 | var parsedUrl = parseUrl(url); 52 | 53 | // Prepare crx object with default values 54 | var crxConfig = { 55 | url: parsedUrl.href, // href appends trailing slash to hostnames 56 | hostname: parsedUrl.hostname, 57 | parsedUrl: parsedUrl, 58 | title: parsedUrl.hostname, 59 | filename: parsedUrl.hostname + '.crx' 60 | }; 61 | 62 | // Normalize hostname for custom use-cases 63 | crxConfig.host = parsedUrl.hostname.toLowerCase().replace('www.', ''); 64 | 65 | var $; 66 | 67 | try { 68 | // Execute GET request to provided URL - may fail for internal URLs, continue anyway 69 | $ = yield getPageDOM(crxConfig.url); 70 | } 71 | catch (exc) { 72 | // Ignore exception, continue execution ($ is optional) 73 | } 74 | 75 | // Get extension title and handle custom cases 76 | crxConfig.title = getCrxTitle($, crxConfig, ctx); 77 | 78 | // Get extension icon path (either from default host icons, page DOM, or a user-uploaded icon) 79 | crxConfig.icon = yield getCrxIcon($, crxConfig, ctx); 80 | 81 | // Get extension frame color (advanced option) 82 | crxConfig.frameColor = getCrxFrameColor(ctx); 83 | 84 | // Get extra permissions (advanced options) 85 | crxConfig.permissions = getExtraPermissions(ctx); 86 | 87 | // Get custom user agent (optional) 88 | crxConfig.userAgent = ctx.body.userAgent; 89 | 90 | // Check whether user wants internal links 91 | crxConfig.internalLinks = ctx.body.internalLinks !== undefined; 92 | 93 | // Return the extension configuration object 94 | return crxConfig; 95 | } 96 | 97 | function* getCrxIcon($, crxConfig, ctx) { 98 | // Read any uploaded files from the post body (do this before reading from parts.fields) 99 | var customIcon = ctx.body.files.icon; 100 | 101 | // Check if the user uploaded a custom icon 102 | if (customIcon && customIcon.size > 0 && customIcon.type == 'image/png') { 103 | // Store tmp icon path and delete it afterwards 104 | crxConfig.tmpIconPath = customIcon.path; 105 | 106 | // Return the path to the uploaded custom icon 107 | return customIcon.path; 108 | } 109 | else { 110 | // Remove invalid uploaded files 111 | fs.removeSync(customIcon.path); 112 | } 113 | 114 | // Check if we have a default icon for this host (if so, it should override the web app's favicon) 115 | var defaultHostIcon = getDefaultHostIconPath(crxConfig); 116 | 117 | // Got one? 118 | if (defaultHostIcon) { 119 | // Return it as the icon's path 120 | return defaultHostIcon; 121 | } 122 | 123 | // Got page DOM? 124 | if ($) { 125 | // Shortcut icon selectors (first ones are the highest quality) 126 | var selectors = [ 127 | 'link[rel="fluid-icon"]', 128 | 'link[rel="apple-touch-icon"]', 129 | 'link[rel="icon"]', 130 | 'link[rel="shortcut icon"]' 131 | ]; 132 | 133 | // The actual href value of one of these selectors 134 | var linkHref; 135 | 136 | // Traverse selectors, find first one that exists 137 | for (var i in selectors) { 138 | // Get element by selector 139 | var element = $(selectors[i]); 140 | 141 | // Found it? 142 | if (element.length > 0) { 143 | // Set linkHref to 's href attribute 144 | linkHref = element.attr('href'); 145 | break; 146 | } 147 | } 148 | 149 | // Got a valid image URL? 150 | if (linkHref) { 151 | // Construct a temporary upload path on the server 152 | var tmpUploadPath = path.join(os.tmpdir(), Math.random().toString() + '.png'); 153 | 154 | // Convert relative icon path to absolute 155 | var absoluteIconUrl = url.resolve(crxConfig.url, linkHref); 156 | 157 | // Resolve succeeded? 158 | if (absoluteIconUrl) { 159 | // Download it locally to the tmp path 160 | yield downloadFile(absoluteIconUrl, tmpUploadPath); 161 | } 162 | 163 | // Store tmp icon path and delete it afterwards 164 | crxConfig.tmpIconPath = tmpUploadPath; 165 | 166 | // Return the path to the downloaded icon 167 | return tmpUploadPath; 168 | } 169 | } 170 | 171 | // Fallback to generic letter icon: grab first char of hostname (hopefully a letter) 172 | var letter = crxConfig.parsedUrl.hostname.substring(0, 1).toUpperCase(); 173 | 174 | // Not an English letter? 175 | if (!letter.match(/[A-Z]/)) { 176 | // Use default applicationize icon 177 | return null; 178 | } 179 | 180 | // Build path to placeholder letter icon 181 | return path.join(__dirname, '../../assets/icons/fallback/' + letter + '.png'); 182 | } 183 | 184 | function getCrxTitle($, crxConfig, ctx) { 185 | // Specified a custom title for the extension? 186 | if (ctx.body.title) { 187 | // Return it (it should override all other title extraction mechanisms) 188 | return ctx.body.title; 189 | } 190 | 191 | // No DOM? 192 | if (!$) { 193 | // Return current extension title (the web app's hostname) 194 | return crxConfig.title; 195 | } 196 | 197 | // Extract extension title from the dom's tag 198 | var title = $('title').text().trim() || crxConfig.parsedUrl.hostname; 199 | 200 | // Handle custom use-cases per hostname 201 | switch (crxConfig.host) { 202 | case 'messenger.com': 203 | // Fix weird 0x8234 chars in FB messenger <title> 204 | title = 'Messenger'; 205 | break; 206 | case 'keep.google.com': 207 | // Avoid "Sign In - Google Accounts" 208 | title = 'Google Keep'; 209 | break; 210 | } 211 | 212 | return title; 213 | } 214 | 215 | function getExtraPermissions(ctx) { 216 | // Extra permissions list 217 | var permissions = []; 218 | 219 | // Notifications 220 | if (ctx.body.notifications) { 221 | permissions.push('notifications'); 222 | } 223 | 224 | // Microphone 225 | if (ctx.body.audioCapture) { 226 | permissions.push('audioCapture'); 227 | } 228 | 229 | // Webcam 230 | if (ctx.body.videoCapture) { 231 | permissions.push('videoCapture'); 232 | } 233 | 234 | // We're good to go 235 | return permissions; 236 | } 237 | 238 | function getCrxFrameColor(ctx) { 239 | // Extract frame color from input 240 | var frameColor = ctx.body.frameColor; 241 | 242 | // Didn't specify a custom frame color for the extension? 243 | if (!frameColor) { 244 | // Use default frame color 245 | return null; 246 | } 247 | 248 | // Make sure it's a valid hex value (Chrome won't accept anything else) 249 | if (!validator.isHexColor(frameColor)) { 250 | throw { status: 400, message: 'Please provide a valid hex color value to customize the frame color.' }; 251 | } 252 | 253 | // We're good to go 254 | return frameColor; 255 | } 256 | 257 | function parseUrl(targetUrl) { 258 | // Parse URL (to retrieve hostname and verify its validity) 259 | var parsedUrl = url.parse(targetUrl); 260 | 261 | // Parse failed? 262 | if (!parsedUrl || !parsedUrl.protocol || !parsedUrl.host || parsedUrl.protocol.indexOf('http') == -1) { 263 | throw { status: 400, message: 'Please provide a valid URL for your extension. (It must start with http(s)://)' }; 264 | } 265 | 266 | // Valid URL 267 | return parsedUrl; 268 | } 269 | 270 | function* getPageDOM(url) { 271 | // Prepare request (send fake browser user-agent header) 272 | var req = { 273 | url: url, 274 | timeout: 7000, 275 | headers: { 276 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36' 277 | } 278 | }; 279 | 280 | // Actually execute the request 281 | var response = yield requestThunk(req); 282 | 283 | // Load DOM into Cheerio (HTML parser) 284 | return cheerio.load(response[0].body); 285 | } 286 | 287 | function* generateZipArchive(crxBuffer) { 288 | // Create temporary stream for CRX buffer 289 | var bufferStream = new stream.PassThrough(); 290 | 291 | // Write CRX buffer to stream 292 | bufferStream.end(crxBuffer); 293 | 294 | // Pipe CRX buffer into a new GZip archive 295 | return bufferStream.pipe(zlib.createGzip()); 296 | } 297 | 298 | function* generateCrx(crxConfig) { 299 | // Generate pem certificate 300 | var cert = yield createCertificate({ days: 365 * 10, selfSigned: true }); 301 | 302 | // Init new .crx extension with our private key 303 | var crx = new Extension({ privateKey: cert.clientKey }); 304 | 305 | // Tmp crx staging path 306 | var crxPath = path.join(__dirname, '../../assets/staging'); 307 | 308 | // Clear all files from previous staging runs 309 | fs.removeSync(crxPath); 310 | 311 | // Copy crx files to tmp staging directory 312 | fs.copySync(path.join(__dirname, '../../assets/crx'), crxPath) 313 | 314 | // Load extension manifest and default icon 315 | yield crx.load(crxPath); 316 | 317 | // Do we have a local icon to copy over? 318 | if (crxConfig.icon && fs.existsSync(crxConfig.icon)) { 319 | // Set target copy path as current extension icon's path 320 | var copyToPath = crx.path + '/' + crx.manifest.icons['128']; 321 | 322 | // Copy the local file to the current extension's folder, where the icon currently resides 323 | yield copyLocalFile(crxConfig.icon, copyToPath); 324 | 325 | // Delete tmp file if exists 326 | if (crxConfig.tmpIconPath) { 327 | fs.removeSync(crxConfig.tmpIconPath); 328 | } 329 | } 330 | 331 | // Customize the crx's manifest.json file 332 | customizeManifestFile(crxConfig, crx); 333 | 334 | // Customize the crx's embed.html file 335 | customizeEmbedFile(crxConfig, crx); 336 | 337 | // Customize the crx's background.js file 338 | customizeBackgroundScript(crxConfig, crx); 339 | 340 | // Pack the extension into a .crx and return its buffer 341 | var crxBuffer = yield crx.pack(); 342 | 343 | // Return buffer 344 | return crxBuffer; 345 | } 346 | 347 | function getDefaultHostIconPath(crxConfig) { 348 | // Build path to the default icon for this host 349 | var iconPath = path.join(__dirname, '../../assets/icons/' + crxConfig.host + '.png'); 350 | 351 | try { 352 | // Check if icon exists 353 | fs.accessSync(iconPath, fs.F_OK); 354 | } 355 | catch (err) { 356 | // No such file 357 | return null; 358 | } 359 | 360 | // File exists 361 | return iconPath; 362 | } 363 | 364 | function customizeManifestFile(crxConfig, crx) { 365 | // Build path to manifest.json 366 | var manifestPath = crx.path + '/manifest.json'; 367 | 368 | // Read its contents (JSON) 369 | var json = fs.readFileSync(manifestPath, 'utf8'); 370 | 371 | // Parse manifest.json 372 | var manifest = JSON.parse(json); 373 | 374 | // Set extension title to page title or custom title 375 | manifest.name = crxConfig.title; 376 | 377 | // Any custom permissions? 378 | if (crxConfig.permissions) { 379 | manifest.permissions = manifest.permissions.concat(crxConfig.permissions); 380 | } 381 | 382 | // Convert back to json string 383 | json = JSON.stringify(manifest); 384 | 385 | // Overwrite the manifest file with the new json 386 | fs.writeFileSync(manifestPath, json); 387 | } 388 | 389 | function customizeEmbedFile(crxConfig, crx) { 390 | // Build path to embed.html 391 | var embedPath = crx.path + '/' + crx.manifest.app.background.pages[0]; 392 | 393 | // Read its contents 394 | var html = fs.readFileSync(embedPath, 'utf8'); 395 | 396 | // Load DOM into Cheerio (HTML parser) 397 | var $ = cheerio.load(html); 398 | 399 | // Set page title to crx title 400 | $('title').text(crxConfig.title); 401 | 402 | // Set webview source to applicationized URL 403 | $('webview').attr('src', crxConfig.url); 404 | 405 | // Convert back to html string 406 | html = $.html(); 407 | 408 | // Overwrite the embed file with the new html 409 | fs.writeFileSync(embedPath, html); 410 | } 411 | 412 | function customizeBackgroundScript(crxConfig, crx) { 413 | // Config object to inject into background.js 414 | var appConfig = { 415 | // App hostname to load in webview 416 | hostname: crxConfig.hostname, 417 | // Pass in a custom user agent 418 | userAgent: crxConfig.userAgent, 419 | // Custom behavior params 420 | behavior: { 421 | // Whether to open external links in Chrome or in a new Applicationize window 422 | internalLinks: crxConfig.internalLinks 423 | }, 424 | // Default params for `chrome.app.window.create` in `background.js` 425 | chromeAppWindow: { 426 | id: 'embed', 427 | frame: { 428 | type: 'chrome' 429 | }, 430 | innerBounds: { 431 | 'width': 1180, 432 | 'height': 900 433 | } 434 | } 435 | }; 436 | 437 | // User picked a custom frame color? 438 | if (crxConfig.frameColor) { 439 | // Insert it into the chrome app window params 440 | appConfig.chromeAppWindow.frame.color = crxConfig.frameColor; 441 | } 442 | 443 | // Build path to background.js 444 | var scriptPath = crx.path + '/' + crx.manifest.app.background.scripts[0]; 445 | 446 | // Read its contents 447 | var js = fs.readFileSync(scriptPath, 'utf8'); 448 | 449 | // Inject chrome app window params into background.js file (replace {inject-background-script-config} with stringified JSON) 450 | js = js.replace(/{inject-background-script-config}/, JSON.stringify(appConfig)); 451 | 452 | // Overwrite the background script with the modified contents 453 | fs.writeFileSync(scriptPath, js); 454 | } 455 | 456 | function sendZipFile(request, crxConfig, zipBuffer) { 457 | // Set content-type to .zip extension mime type 458 | request.set('content-type', 'application/zip'); 459 | 460 | // Set extension filename 461 | request.set('content-disposition', 'attachment; filename=' + crxConfig.filename + '.zip'); 462 | 463 | // Set the request body to the .zip file buffer 464 | request.body = zipBuffer; 465 | } 466 | 467 | function downloadFile(url, filePath) { 468 | // Promisify the request 469 | return new Promise(function (resolve, reject) { 470 | try { 471 | // Create write stream 472 | var stream = fs.createWriteStream(filePath); 473 | 474 | // Wait for finish event 475 | stream.on('finish', function () { 476 | // Resolve the promise 477 | return resolve(true); 478 | }); 479 | 480 | // Listen for errors 481 | stream.on('error', function (error) { 482 | // Log the error 483 | console.log('downloadFile error: ', error); 484 | }); 485 | 486 | // Pipe the request to a file 487 | return request(url).pipe(stream); 488 | } catch (e) { 489 | // Failed 490 | return reject(e); 491 | } 492 | }); 493 | } 494 | 495 | function copyLocalFile(from, to) { 496 | // Promisify the request 497 | return new Promise(function (resolve, reject) { 498 | try { 499 | // Ensure the source file exists 500 | if (!fs.existsSync(from)) { 501 | return reject('The source file does not exist.'); 502 | } 503 | 504 | // Create write stream 505 | var writeStream = fs.createWriteStream(to); 506 | 507 | // Wait for finish event 508 | writeStream.on('finish', function () { 509 | // End the stream 510 | writeStream.end(); 511 | 512 | // Resolve the promise 513 | return resolve(true); 514 | }); 515 | 516 | // Listen for errors 517 | writeStream.on('error', function (error) { 518 | // Log the error 519 | console.log('copyLocalFile error: ', error); 520 | }); 521 | 522 | // Pipe the "from" stream into the "to" stream 523 | fs.createReadStream(from).pipe(writeStream); 524 | } catch (e) { 525 | // Failed 526 | return reject(e); 527 | } 528 | }); 529 | } -------------------------------------------------------------------------------- /lib/routes/now.js: -------------------------------------------------------------------------------- 1 | var send = require('koa-send'); 2 | 3 | // GET /now 4 | module.exports = function* () { 5 | // Send HTML file 6 | yield send(this, './public/now.html'); 7 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "applicationize", 3 | "version": "1.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/eladnava/applicationize" 7 | }, 8 | "description": "Applicationize your favorite web apps by wrapping them inside a Chrome extension that runs standalone like a native desktop app.", 9 | "main": "server.js", 10 | "scripts": { 11 | "start": "forever start -l /home/node/server.log --append server.js", 12 | "stop": "forever stop 0", 13 | "restart": "npm stop; npm start" 14 | }, 15 | "author": "Elad Nava", 16 | "license": "Apache-2.0", 17 | "dependencies": { 18 | "cheerio": "1.0.0-rc.12", 19 | "crx": "^5.0.1", 20 | "fs-extra": "^7.0.1", 21 | "koa": "1.1.2", 22 | "koa-better-body": "^2.0.1", 23 | "koa-route": "2.4.2", 24 | "koa-send": "^3.2.0", 25 | "koa-static": "1.5.2", 26 | "pem": "^1.14.2", 27 | "request": "^2.88.0", 28 | "thunkify": "2.1.2", 29 | "validator": "^13.7.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/css/bootstrap-colorpicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker v2.3.3 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | * 5 | * Originally written by (c) 2012 Stefan Petre 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0.txt 8 | * 9 | */.colorpicker-saturation{width:100px;height:100px;background-image:url(../img/bootstrap-colorpicker/saturation.png);cursor:crosshair;float:left}.colorpicker-saturation i{display:block;height:5px;width:5px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;position:absolute;top:0;left:0;margin:-4px 0 0 -4px}.colorpicker-saturation i b{display:block;height:5px;width:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-alpha,.colorpicker-hue{width:15px;height:100px;float:left;cursor:row-resize;margin-left:4px;margin-bottom:4px}.colorpicker-alpha i,.colorpicker-hue i{display:block;height:1px;background:#000;border-top:1px solid #fff;position:absolute;top:0;left:0;width:100%;margin-top:-1px}.colorpicker-hue{background-image:url(../img/bootstrap-colorpicker/hue.png)}.colorpicker-alpha{background-image:url(../img/bootstrap-colorpicker/alpha.png);display:none}.colorpicker-alpha,.colorpicker-hue,.colorpicker-saturation{background-size:contain}.colorpicker{padding:4px;min-width:130px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;z-index:2500}.colorpicker:after,.colorpicker:before{display:table;content:"";line-height:0}.colorpicker:after{clear:both}.colorpicker:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:6px}.colorpicker:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:7px}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url(../img/bootstrap-colorpicker/alpha.png);background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{cursor:pointer;float:left;height:10px;width:10px}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .add-on i,.colorpicker-element .input-group-addon i{display:inline-block;cursor:pointer;height:16px;vertical-align:text-top;width:16px}.colorpicker.colorpicker-inline{position:relative;display:inline-block;float:none;z-index:auto}.colorpicker.colorpicker-horizontal{width:110px;min-width:110px;height:auto}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-alpha,.colorpicker.colorpicker-horizontal .colorpicker-hue{width:100px;height:15px;float:left;cursor:col-resize;margin-left:0;margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-alpha i,.colorpicker.colorpicker-horizontal .colorpicker-hue i{display:block;height:15px;background:#fff;position:absolute;top:0;left:0;width:1px;border:none;margin-top:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url(../img/bootstrap-colorpicker/hue-horizontal.png)}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url(../img/bootstrap-colorpicker/alpha-horizontal.png)}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{left:auto;right:6px}.colorpicker-right:after{left:auto;right:7px}.colorpicker-no-arrow:before{border-right:0;border-left:0}.colorpicker-no-arrow:after{border-right:0;border-left:0} 10 | /*# sourceMappingURL=bootstrap-colorpicker.min.css.map */ -------------------------------------------------------------------------------- /public/css/custom.css: -------------------------------------------------------------------------------- 1 | .overlay:before { 2 | background: #000; 3 | opacity: 0.5; 4 | } 5 | 6 | .slogan-container { 7 | top: 15%; 8 | } 9 | 10 | .cover .align-bottom { 11 | bottom: 10%; 12 | } 13 | 14 | .feature .icon { 15 | opacity: 0.8; 16 | color: #e31d3b; 17 | } 18 | 19 | .checkbox-option { 20 | margin-bottom: 0; 21 | } 22 | 23 | .fork-me { 24 | top: 0; 25 | right: 0; 26 | border: 0; 27 | z-index: 5; 28 | position: absolute; 29 | } 30 | 31 | .spinner { 32 | display: none; 33 | } 34 | 35 | .logo-container { 36 | top: 15px; 37 | left: 15px; 38 | z-index: 5; 39 | position: absolute; 40 | } 41 | 42 | .input-subtext { 43 | color: inherit; 44 | font-size: 10px; 45 | font-weight: 300; 46 | display: inline-block; 47 | border-bottom: 1px dotted #999; 48 | } 49 | 50 | .advanced-options-container { 51 | display: none; 52 | } 53 | 54 | ::-webkit-input-placeholder { 55 | text-transform: none; 56 | } 57 | 58 | ::-moz-placeholder { 59 | text-transform: none; 60 | } 61 | 62 | :-moz-placeholder { 63 | text-transform: none; 64 | } 65 | 66 | -ms-input-placeholder { 67 | text-transform: none; 68 | } 69 | 70 | @media (max-width: 767px) { 71 | .text-center-mobile { 72 | text-align: center; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/css/flexslider.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery FlexSlider v2.3.0 3 | * http://www.woothemes.com/flexslider/ 4 | * 5 | * Copyright 2012 WooThemes 6 | * Free to use under the GPLv2 license. 7 | * http://www.gnu.org/licenses/gpl-2.0.html 8 | * 9 | * Contributing author: Ville Ristimäki (@villeristi) 10 | * 11 | */ 12 | /* ==================================================================================================================== 13 | * FONT-FACE 14 | * ====================================================================================================================*/ 15 | 16 | /* ==================================================================================================================== 17 | * RESETS 18 | * ====================================================================================================================*/ 19 | .flex-container a:hover, 20 | .flex-slider a:hover, 21 | .flex-container a:focus, 22 | .flex-slider a:focus { 23 | outline: none; 24 | } 25 | .slides, 26 | .flex-control-nav, 27 | .flex-direction-nav { 28 | margin: 0; 29 | padding: 0; 30 | list-style: none; 31 | } 32 | .flex-pauseplay span { 33 | text-transform: capitalize; 34 | } 35 | /* ==================================================================================================================== 36 | * BASE STYLES 37 | * ====================================================================================================================*/ 38 | .flexslider { 39 | margin: 0; 40 | padding: 0; 41 | } 42 | .flexslider .slides > li { 43 | display: none; 44 | -webkit-backface-visibility: hidden; 45 | } 46 | .flexslider .slides img { 47 | width: 100%; 48 | display: block; 49 | } 50 | .flexslider .slides:after { 51 | content: "\0020"; 52 | display: block; 53 | clear: both; 54 | visibility: hidden; 55 | line-height: 0; 56 | height: 0; 57 | } 58 | html[xmlns] .flexslider .slides { 59 | display: block; 60 | } 61 | * html .flexslider .slides { 62 | height: 1%; 63 | } 64 | .no-js .flexslider .slides > li:first-child { 65 | display: block; 66 | } 67 | /* ==================================================================================================================== 68 | * DEFAULT THEME 69 | * ====================================================================================================================*/ 70 | .flexslider { 71 | margin: 0 0 60px; 72 | background: #fff; 73 | border: 4px solid #fff; 74 | position: relative; 75 | zoom: 1; 76 | -webkit-border-radius: 4px; 77 | -moz-border-radius: 4px; 78 | border-radius: 4px; 79 | -webkit-box-shadow: '' 0 1px 4px rgba(0, 0, 0, 0.2); 80 | -moz-box-shadow: '' 0 1px 4px rgba(0, 0, 0, 0.2); 81 | -o-box-shadow: '' 0 1px 4px rgba(0, 0, 0, 0.2); 82 | box-shadow: '' 0 1px 4px rgba(0, 0, 0, 0.2); 83 | } 84 | .flexslider .slides { 85 | zoom: 1; 86 | } 87 | .flex-viewport { 88 | max-height: 2000px; 89 | -webkit-transition: all 1s ease; 90 | -moz-transition: all 1s ease; 91 | -ms-transition: all 1s ease; 92 | -o-transition: all 1s ease; 93 | transition: all 1s ease; 94 | } 95 | .loading .flex-viewport { 96 | max-height: 300px; 97 | } 98 | .carousel li { 99 | margin-right: 5px; 100 | } 101 | .flex-direction-nav { 102 | *height: 0; 103 | } 104 | .flex-direction-nav a { 105 | text-decoration: none; 106 | display: block; 107 | width: 40px; 108 | height: 40px; 109 | margin: -20px 0 0; 110 | position: absolute; 111 | top: 50%; 112 | z-index: 10; 113 | overflow: hidden; 114 | opacity: 0; 115 | cursor: pointer; 116 | color: rgba(0, 0, 0, 0.8); 117 | text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.3); 118 | -webkit-transition: all 0.3s ease-in-out; 119 | -moz-transition: all 0.3s ease-in-out; 120 | -ms-transition: all 0.3s ease-in-out; 121 | -o-transition: all 0.3s ease-in-out; 122 | transition: all 0.3s ease-in-out; 123 | } 124 | .flex-direction-nav a:before { 125 | font-family: "flexslider-icon"; 126 | font-size: 40px; 127 | display: inline-block; 128 | content: '\f001'; 129 | } 130 | .flex-direction-nav a.flex-next:before { 131 | content: '\f002'; 132 | } 133 | .flex-direction-nav .flex-prev { 134 | left: -50px; 135 | } 136 | .flex-direction-nav .flex-next { 137 | right: -50px; 138 | text-align: right; 139 | } 140 | .flexslider:hover .flex-direction-nav .flex-prev { 141 | opacity: 0.7; 142 | left: 10px; 143 | } 144 | .flexslider:hover .flex-direction-nav .flex-prev:hover { 145 | opacity: 1; 146 | } 147 | .flexslider:hover .flex-direction-nav .flex-next { 148 | opacity: 0.7; 149 | right: 10px; 150 | } 151 | .flexslider:hover .flex-direction-nav .flex-next:hover { 152 | opacity: 1; 153 | } 154 | .flex-direction-nav .flex-disabled { 155 | opacity: 0!important; 156 | filter: alpha(opacity=0); 157 | cursor: default; 158 | } 159 | .flex-pauseplay a { 160 | display: block; 161 | width: 20px; 162 | height: 20px; 163 | position: absolute; 164 | bottom: 5px; 165 | left: 10px; 166 | opacity: 0.8; 167 | z-index: 10; 168 | overflow: hidden; 169 | cursor: pointer; 170 | color: #000; 171 | } 172 | .flex-pauseplay a:before { 173 | font-family: "flexslider-icon"; 174 | font-size: 20px; 175 | display: inline-block; 176 | content: '\f004'; 177 | } 178 | .flex-pauseplay a:hover { 179 | opacity: 1; 180 | } 181 | .flex-pauseplay a .flex-play:before { 182 | content: '\f003'; 183 | } 184 | .flex-control-nav { 185 | width: 100%; 186 | position: absolute; 187 | bottom: -40px; 188 | text-align: center; 189 | } 190 | .flex-control-nav li { 191 | margin: 0 6px; 192 | display: inline-block; 193 | zoom: 1; 194 | *display: inline; 195 | } 196 | .flex-control-paging li a { 197 | width: 11px; 198 | height: 11px; 199 | display: block; 200 | background: #666; 201 | background: rgba(0, 0, 0, 0.5); 202 | cursor: pointer; 203 | text-indent: -9999px; 204 | -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); 205 | -moz-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); 206 | -o-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); 207 | box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); 208 | -webkit-border-radius: 20px; 209 | -moz-border-radius: 20px; 210 | border-radius: 20px; 211 | } 212 | .flex-control-paging li a:hover { 213 | background: #333; 214 | background: rgba(0, 0, 0, 0.7); 215 | } 216 | .flex-control-paging li a.flex-active { 217 | background: #000; 218 | background: rgba(0, 0, 0, 0.9); 219 | cursor: default; 220 | } 221 | .flex-control-thumbs { 222 | margin: 5px 0 0; 223 | position: static; 224 | overflow: hidden; 225 | } 226 | .flex-control-thumbs li { 227 | width: 25%; 228 | float: left; 229 | margin: 0; 230 | } 231 | .flex-control-thumbs img { 232 | width: 100%; 233 | display: block; 234 | opacity: .7; 235 | cursor: pointer; 236 | -webkit-transition: all 1s ease; 237 | -moz-transition: all 1s ease; 238 | -ms-transition: all 1s ease; 239 | -o-transition: all 1s ease; 240 | transition: all 1s ease; 241 | } 242 | .flex-control-thumbs img:hover { 243 | opacity: 1; 244 | } 245 | .flex-control-thumbs .flex-active { 246 | opacity: 1; 247 | cursor: default; 248 | } 249 | /* ==================================================================================================================== 250 | * RESPONSIVE 251 | * ====================================================================================================================*/ 252 | @media screen and (max-width: 860px) { 253 | .flex-direction-nav .flex-prev { 254 | opacity: 1; 255 | left: 10px; 256 | } 257 | .flex-direction-nav .flex-next { 258 | opacity: 1; 259 | right: 10px; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /public/css/lightbox.min.css: -------------------------------------------------------------------------------- 1 | body:after{content:url(../img/lightbox/close.png) url(../img/lightbox/loading.gif) url(../img/lightbox/prev.png) url(../img/lightbox/next.png);display:none}.lightboxOverlay{position:absolute;top:0;left:0;z-index:9999;background-color:#000;filter:alpha(Opacity=80);opacity:.8;display:none}.lightbox{position:absolute;left:0;width:100%;z-index:10000;text-align:center;line-height:0;font-weight:400}.lightbox .lb-image{display:block;height:auto;max-width:inherit;-webkit-border-radius:3px;-moz-border-radius:3px;-ms-border-radius:3px;-o-border-radius:3px;border-radius:3px}.lightbox a img{border:none}.lb-outerContainer{position:relative;background-color:#fff;*zoom:1;width:250px;height:250px;margin:0 auto;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.lb-outerContainer:after{content:"";display:table;clear:both}.lb-container{padding:4px}.lb-loader{position:absolute;top:43%;left:0;height:25%;width:100%;text-align:center;line-height:0}.lb-cancel{display:block;width:32px;height:32px;margin:0 auto;background:url(../img/lightbox/loading.gif) no-repeat}.lb-nav{position:absolute;top:0;left:0;height:100%;width:100%;z-index:10}.lb-container>.nav{left:0}.lb-nav a{outline:0;background-image:url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}.lb-next,.lb-prev{height:100%;cursor:pointer;display:block}.lb-nav a.lb-prev{width:34%;left:0;float:left;background:url(../img/lightbox/prev.png) left 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-prev:hover{filter:alpha(Opacity=100);opacity:1}.lb-nav a.lb-next{width:64%;right:0;float:right;background:url(../img/lightbox/next.png) right 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-next:hover{filter:alpha(Opacity=100);opacity:1}.lb-dataContainer{margin:0 auto;padding-top:5px;*zoom:1;width:100%;-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.lb-dataContainer:after{content:"";display:table;clear:both}.lb-data{padding:0 4px;color:#ccc}.lb-data .lb-details{width:85%;float:left;text-align:left;line-height:1.1em}.lb-data .lb-caption{font-size:13px;font-weight:700;line-height:1em}.lb-data .lb-number{display:block;clear:left;padding-bottom:1em;font-size:12px;color:#999}.lb-data .lb-close{display:block;float:right;width:30px;height:30px;background:url(../img/lightbox/close.png) top right no-repeat;text-align:right;outline:0;filter:alpha(Opacity=70);opacity:.7;-webkit-transition:opacity .2s;-moz-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.lb-data .lb-close:hover{cursor:pointer;filter:alpha(Opacity=100);opacity:1} -------------------------------------------------------------------------------- /public/css/themify-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'themify'; 3 | src:url('../fonts/themify.eot?'); 4 | src:url('../fonts/themify.eot?#iefix-fvbane') format('embedded-opentype'), 5 | url('../fonts/themify.woff') format('woff'), 6 | url('../fonts/themify.ttf') format('truetype'), 7 | url('../fonts/themify.svg') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="ti-"], [class*=" ti-"] { 13 | font-family: 'themify'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .ti-wand:before { 27 | content: "\e600"; 28 | } 29 | .ti-volume:before { 30 | content: "\e601"; 31 | } 32 | .ti-user:before { 33 | content: "\e602"; 34 | } 35 | .ti-unlock:before { 36 | content: "\e603"; 37 | } 38 | .ti-unlink:before { 39 | content: "\e604"; 40 | } 41 | .ti-trash:before { 42 | content: "\e605"; 43 | } 44 | .ti-thought:before { 45 | content: "\e606"; 46 | } 47 | .ti-target:before { 48 | content: "\e607"; 49 | } 50 | .ti-tag:before { 51 | content: "\e608"; 52 | } 53 | .ti-tablet:before { 54 | content: "\e609"; 55 | } 56 | .ti-star:before { 57 | content: "\e60a"; 58 | } 59 | .ti-spray:before { 60 | content: "\e60b"; 61 | } 62 | .ti-signal:before { 63 | content: "\e60c"; 64 | } 65 | .ti-shopping-cart:before { 66 | content: "\e60d"; 67 | } 68 | .ti-shopping-cart-full:before { 69 | content: "\e60e"; 70 | } 71 | .ti-settings:before { 72 | content: "\e60f"; 73 | } 74 | .ti-search:before { 75 | content: "\e610"; 76 | } 77 | .ti-zoom-in:before { 78 | content: "\e611"; 79 | } 80 | .ti-zoom-out:before { 81 | content: "\e612"; 82 | } 83 | .ti-cut:before { 84 | content: "\e613"; 85 | } 86 | .ti-ruler:before { 87 | content: "\e614"; 88 | } 89 | .ti-ruler-pencil:before { 90 | content: "\e615"; 91 | } 92 | .ti-ruler-alt:before { 93 | content: "\e616"; 94 | } 95 | .ti-bookmark:before { 96 | content: "\e617"; 97 | } 98 | .ti-bookmark-alt:before { 99 | content: "\e618"; 100 | } 101 | .ti-reload:before { 102 | content: "\e619"; 103 | } 104 | .ti-plus:before { 105 | content: "\e61a"; 106 | } 107 | .ti-pin:before { 108 | content: "\e61b"; 109 | } 110 | .ti-pencil:before { 111 | content: "\e61c"; 112 | } 113 | .ti-pencil-alt:before { 114 | content: "\e61d"; 115 | } 116 | .ti-paint-roller:before { 117 | content: "\e61e"; 118 | } 119 | .ti-paint-bucket:before { 120 | content: "\e61f"; 121 | } 122 | .ti-na:before { 123 | content: "\e620"; 124 | } 125 | .ti-mobile:before { 126 | content: "\e621"; 127 | } 128 | .ti-minus:before { 129 | content: "\e622"; 130 | } 131 | .ti-medall:before { 132 | content: "\e623"; 133 | } 134 | .ti-medall-alt:before { 135 | content: "\e624"; 136 | } 137 | .ti-marker:before { 138 | content: "\e625"; 139 | } 140 | .ti-marker-alt:before { 141 | content: "\e626"; 142 | } 143 | .ti-arrow-up:before { 144 | content: "\e627"; 145 | } 146 | .ti-arrow-right:before { 147 | content: "\e628"; 148 | } 149 | .ti-arrow-left:before { 150 | content: "\e629"; 151 | } 152 | .ti-arrow-down:before { 153 | content: "\e62a"; 154 | } 155 | .ti-lock:before { 156 | content: "\e62b"; 157 | } 158 | .ti-location-arrow:before { 159 | content: "\e62c"; 160 | } 161 | .ti-link:before { 162 | content: "\e62d"; 163 | } 164 | .ti-layout:before { 165 | content: "\e62e"; 166 | } 167 | .ti-layers:before { 168 | content: "\e62f"; 169 | } 170 | .ti-layers-alt:before { 171 | content: "\e630"; 172 | } 173 | .ti-key:before { 174 | content: "\e631"; 175 | } 176 | .ti-import:before { 177 | content: "\e632"; 178 | } 179 | .ti-image:before { 180 | content: "\e633"; 181 | } 182 | .ti-heart:before { 183 | content: "\e634"; 184 | } 185 | .ti-heart-broken:before { 186 | content: "\e635"; 187 | } 188 | .ti-hand-stop:before { 189 | content: "\e636"; 190 | } 191 | .ti-hand-open:before { 192 | content: "\e637"; 193 | } 194 | .ti-hand-drag:before { 195 | content: "\e638"; 196 | } 197 | .ti-folder:before { 198 | content: "\e639"; 199 | } 200 | .ti-flag:before { 201 | content: "\e63a"; 202 | } 203 | .ti-flag-alt:before { 204 | content: "\e63b"; 205 | } 206 | .ti-flag-alt-2:before { 207 | content: "\e63c"; 208 | } 209 | .ti-eye:before { 210 | content: "\e63d"; 211 | } 212 | .ti-export:before { 213 | content: "\e63e"; 214 | } 215 | .ti-exchange-vertical:before { 216 | content: "\e63f"; 217 | } 218 | .ti-desktop:before { 219 | content: "\e640"; 220 | } 221 | .ti-cup:before { 222 | content: "\e641"; 223 | } 224 | .ti-crown:before { 225 | content: "\e642"; 226 | } 227 | .ti-comments:before { 228 | content: "\e643"; 229 | } 230 | .ti-comment:before { 231 | content: "\e644"; 232 | } 233 | .ti-comment-alt:before { 234 | content: "\e645"; 235 | } 236 | .ti-close:before { 237 | content: "\e646"; 238 | } 239 | .ti-clip:before { 240 | content: "\e647"; 241 | } 242 | .ti-angle-up:before { 243 | content: "\e648"; 244 | } 245 | .ti-angle-right:before { 246 | content: "\e649"; 247 | } 248 | .ti-angle-left:before { 249 | content: "\e64a"; 250 | } 251 | .ti-angle-down:before { 252 | content: "\e64b"; 253 | } 254 | .ti-check:before { 255 | content: "\e64c"; 256 | } 257 | .ti-check-box:before { 258 | content: "\e64d"; 259 | } 260 | .ti-camera:before { 261 | content: "\e64e"; 262 | } 263 | .ti-announcement:before { 264 | content: "\e64f"; 265 | } 266 | .ti-brush:before { 267 | content: "\e650"; 268 | } 269 | .ti-briefcase:before { 270 | content: "\e651"; 271 | } 272 | .ti-bolt:before { 273 | content: "\e652"; 274 | } 275 | .ti-bolt-alt:before { 276 | content: "\e653"; 277 | } 278 | .ti-blackboard:before { 279 | content: "\e654"; 280 | } 281 | .ti-bag:before { 282 | content: "\e655"; 283 | } 284 | .ti-move:before { 285 | content: "\e656"; 286 | } 287 | .ti-arrows-vertical:before { 288 | content: "\e657"; 289 | } 290 | .ti-arrows-horizontal:before { 291 | content: "\e658"; 292 | } 293 | .ti-fullscreen:before { 294 | content: "\e659"; 295 | } 296 | .ti-arrow-top-right:before { 297 | content: "\e65a"; 298 | } 299 | .ti-arrow-top-left:before { 300 | content: "\e65b"; 301 | } 302 | .ti-arrow-circle-up:before { 303 | content: "\e65c"; 304 | } 305 | .ti-arrow-circle-right:before { 306 | content: "\e65d"; 307 | } 308 | .ti-arrow-circle-left:before { 309 | content: "\e65e"; 310 | } 311 | .ti-arrow-circle-down:before { 312 | content: "\e65f"; 313 | } 314 | .ti-angle-double-up:before { 315 | content: "\e660"; 316 | } 317 | .ti-angle-double-right:before { 318 | content: "\e661"; 319 | } 320 | .ti-angle-double-left:before { 321 | content: "\e662"; 322 | } 323 | .ti-angle-double-down:before { 324 | content: "\e663"; 325 | } 326 | .ti-zip:before { 327 | content: "\e664"; 328 | } 329 | .ti-world:before { 330 | content: "\e665"; 331 | } 332 | .ti-wheelchair:before { 333 | content: "\e666"; 334 | } 335 | .ti-view-list:before { 336 | content: "\e667"; 337 | } 338 | .ti-view-list-alt:before { 339 | content: "\e668"; 340 | } 341 | .ti-view-grid:before { 342 | content: "\e669"; 343 | } 344 | .ti-uppercase:before { 345 | content: "\e66a"; 346 | } 347 | .ti-upload:before { 348 | content: "\e66b"; 349 | } 350 | .ti-underline:before { 351 | content: "\e66c"; 352 | } 353 | .ti-truck:before { 354 | content: "\e66d"; 355 | } 356 | .ti-timer:before { 357 | content: "\e66e"; 358 | } 359 | .ti-ticket:before { 360 | content: "\e66f"; 361 | } 362 | .ti-thumb-up:before { 363 | content: "\e670"; 364 | } 365 | .ti-thumb-down:before { 366 | content: "\e671"; 367 | } 368 | .ti-text:before { 369 | content: "\e672"; 370 | } 371 | .ti-stats-up:before { 372 | content: "\e673"; 373 | } 374 | .ti-stats-down:before { 375 | content: "\e674"; 376 | } 377 | .ti-split-v:before { 378 | content: "\e675"; 379 | } 380 | .ti-split-h:before { 381 | content: "\e676"; 382 | } 383 | .ti-smallcap:before { 384 | content: "\e677"; 385 | } 386 | .ti-shine:before { 387 | content: "\e678"; 388 | } 389 | .ti-shift-right:before { 390 | content: "\e679"; 391 | } 392 | .ti-shift-left:before { 393 | content: "\e67a"; 394 | } 395 | .ti-shield:before { 396 | content: "\e67b"; 397 | } 398 | .ti-notepad:before { 399 | content: "\e67c"; 400 | } 401 | .ti-server:before { 402 | content: "\e67d"; 403 | } 404 | .ti-quote-right:before { 405 | content: "\e67e"; 406 | } 407 | .ti-quote-left:before { 408 | content: "\e67f"; 409 | } 410 | .ti-pulse:before { 411 | content: "\e680"; 412 | } 413 | .ti-printer:before { 414 | content: "\e681"; 415 | } 416 | .ti-power-off:before { 417 | content: "\e682"; 418 | } 419 | .ti-plug:before { 420 | content: "\e683"; 421 | } 422 | .ti-pie-chart:before { 423 | content: "\e684"; 424 | } 425 | .ti-paragraph:before { 426 | content: "\e685"; 427 | } 428 | .ti-panel:before { 429 | content: "\e686"; 430 | } 431 | .ti-package:before { 432 | content: "\e687"; 433 | } 434 | .ti-music:before { 435 | content: "\e688"; 436 | } 437 | .ti-music-alt:before { 438 | content: "\e689"; 439 | } 440 | .ti-mouse:before { 441 | content: "\e68a"; 442 | } 443 | .ti-mouse-alt:before { 444 | content: "\e68b"; 445 | } 446 | .ti-money:before { 447 | content: "\e68c"; 448 | } 449 | .ti-microphone:before { 450 | content: "\e68d"; 451 | } 452 | .ti-menu:before { 453 | content: "\e68e"; 454 | } 455 | .ti-menu-alt:before { 456 | content: "\e68f"; 457 | } 458 | .ti-map:before { 459 | content: "\e690"; 460 | } 461 | .ti-map-alt:before { 462 | content: "\e691"; 463 | } 464 | .ti-loop:before { 465 | content: "\e692"; 466 | } 467 | .ti-location-pin:before { 468 | content: "\e693"; 469 | } 470 | .ti-list:before { 471 | content: "\e694"; 472 | } 473 | .ti-light-bulb:before { 474 | content: "\e695"; 475 | } 476 | .ti-Italic:before { 477 | content: "\e696"; 478 | } 479 | .ti-info:before { 480 | content: "\e697"; 481 | } 482 | .ti-infinite:before { 483 | content: "\e698"; 484 | } 485 | .ti-id-badge:before { 486 | content: "\e699"; 487 | } 488 | .ti-hummer:before { 489 | content: "\e69a"; 490 | } 491 | .ti-home:before { 492 | content: "\e69b"; 493 | } 494 | .ti-help:before { 495 | content: "\e69c"; 496 | } 497 | .ti-headphone:before { 498 | content: "\e69d"; 499 | } 500 | .ti-harddrives:before { 501 | content: "\e69e"; 502 | } 503 | .ti-harddrive:before { 504 | content: "\e69f"; 505 | } 506 | .ti-gift:before { 507 | content: "\e6a0"; 508 | } 509 | .ti-game:before { 510 | content: "\e6a1"; 511 | } 512 | .ti-filter:before { 513 | content: "\e6a2"; 514 | } 515 | .ti-files:before { 516 | content: "\e6a3"; 517 | } 518 | .ti-file:before { 519 | content: "\e6a4"; 520 | } 521 | .ti-eraser:before { 522 | content: "\e6a5"; 523 | } 524 | .ti-envelope:before { 525 | content: "\e6a6"; 526 | } 527 | .ti-download:before { 528 | content: "\e6a7"; 529 | } 530 | .ti-direction:before { 531 | content: "\e6a8"; 532 | } 533 | .ti-direction-alt:before { 534 | content: "\e6a9"; 535 | } 536 | .ti-dashboard:before { 537 | content: "\e6aa"; 538 | } 539 | .ti-control-stop:before { 540 | content: "\e6ab"; 541 | } 542 | .ti-control-shuffle:before { 543 | content: "\e6ac"; 544 | } 545 | .ti-control-play:before { 546 | content: "\e6ad"; 547 | } 548 | .ti-control-pause:before { 549 | content: "\e6ae"; 550 | } 551 | .ti-control-forward:before { 552 | content: "\e6af"; 553 | } 554 | .ti-control-backward:before { 555 | content: "\e6b0"; 556 | } 557 | .ti-cloud:before { 558 | content: "\e6b1"; 559 | } 560 | .ti-cloud-up:before { 561 | content: "\e6b2"; 562 | } 563 | .ti-cloud-down:before { 564 | content: "\e6b3"; 565 | } 566 | .ti-clipboard:before { 567 | content: "\e6b4"; 568 | } 569 | .ti-car:before { 570 | content: "\e6b5"; 571 | } 572 | .ti-calendar:before { 573 | content: "\e6b6"; 574 | } 575 | .ti-book:before { 576 | content: "\e6b7"; 577 | } 578 | .ti-bell:before { 579 | content: "\e6b8"; 580 | } 581 | .ti-basketball:before { 582 | content: "\e6b9"; 583 | } 584 | .ti-bar-chart:before { 585 | content: "\e6ba"; 586 | } 587 | .ti-bar-chart-alt:before { 588 | content: "\e6bb"; 589 | } 590 | .ti-back-right:before { 591 | content: "\e6bc"; 592 | } 593 | .ti-back-left:before { 594 | content: "\e6bd"; 595 | } 596 | .ti-arrows-corner:before { 597 | content: "\e6be"; 598 | } 599 | .ti-archive:before { 600 | content: "\e6bf"; 601 | } 602 | .ti-anchor:before { 603 | content: "\e6c0"; 604 | } 605 | .ti-align-right:before { 606 | content: "\e6c1"; 607 | } 608 | .ti-align-left:before { 609 | content: "\e6c2"; 610 | } 611 | .ti-align-justify:before { 612 | content: "\e6c3"; 613 | } 614 | .ti-align-center:before { 615 | content: "\e6c4"; 616 | } 617 | .ti-alert:before { 618 | content: "\e6c5"; 619 | } 620 | .ti-alarm-clock:before { 621 | content: "\e6c6"; 622 | } 623 | .ti-agenda:before { 624 | content: "\e6c7"; 625 | } 626 | .ti-write:before { 627 | content: "\e6c8"; 628 | } 629 | .ti-window:before { 630 | content: "\e6c9"; 631 | } 632 | .ti-widgetized:before { 633 | content: "\e6ca"; 634 | } 635 | .ti-widget:before { 636 | content: "\e6cb"; 637 | } 638 | .ti-widget-alt:before { 639 | content: "\e6cc"; 640 | } 641 | .ti-wallet:before { 642 | content: "\e6cd"; 643 | } 644 | .ti-video-clapper:before { 645 | content: "\e6ce"; 646 | } 647 | .ti-video-camera:before { 648 | content: "\e6cf"; 649 | } 650 | .ti-vector:before { 651 | content: "\e6d0"; 652 | } 653 | .ti-themify-logo:before { 654 | content: "\e6d1"; 655 | } 656 | .ti-themify-favicon:before { 657 | content: "\e6d2"; 658 | } 659 | .ti-themify-favicon-alt:before { 660 | content: "\e6d3"; 661 | } 662 | .ti-support:before { 663 | content: "\e6d4"; 664 | } 665 | .ti-stamp:before { 666 | content: "\e6d5"; 667 | } 668 | .ti-split-v-alt:before { 669 | content: "\e6d6"; 670 | } 671 | .ti-slice:before { 672 | content: "\e6d7"; 673 | } 674 | .ti-shortcode:before { 675 | content: "\e6d8"; 676 | } 677 | .ti-shift-right-alt:before { 678 | content: "\e6d9"; 679 | } 680 | .ti-shift-left-alt:before { 681 | content: "\e6da"; 682 | } 683 | .ti-ruler-alt-2:before { 684 | content: "\e6db"; 685 | } 686 | .ti-receipt:before { 687 | content: "\e6dc"; 688 | } 689 | .ti-pin2:before { 690 | content: "\e6dd"; 691 | } 692 | .ti-pin-alt:before { 693 | content: "\e6de"; 694 | } 695 | .ti-pencil-alt2:before { 696 | content: "\e6df"; 697 | } 698 | .ti-palette:before { 699 | content: "\e6e0"; 700 | } 701 | .ti-more:before { 702 | content: "\e6e1"; 703 | } 704 | .ti-more-alt:before { 705 | content: "\e6e2"; 706 | } 707 | .ti-microphone-alt:before { 708 | content: "\e6e3"; 709 | } 710 | .ti-magnet:before { 711 | content: "\e6e4"; 712 | } 713 | .ti-line-double:before { 714 | content: "\e6e5"; 715 | } 716 | .ti-line-dotted:before { 717 | content: "\e6e6"; 718 | } 719 | .ti-line-dashed:before { 720 | content: "\e6e7"; 721 | } 722 | .ti-layout-width-full:before { 723 | content: "\e6e8"; 724 | } 725 | .ti-layout-width-default:before { 726 | content: "\e6e9"; 727 | } 728 | .ti-layout-width-default-alt:before { 729 | content: "\e6ea"; 730 | } 731 | .ti-layout-tab:before { 732 | content: "\e6eb"; 733 | } 734 | .ti-layout-tab-window:before { 735 | content: "\e6ec"; 736 | } 737 | .ti-layout-tab-v:before { 738 | content: "\e6ed"; 739 | } 740 | .ti-layout-tab-min:before { 741 | content: "\e6ee"; 742 | } 743 | .ti-layout-slider:before { 744 | content: "\e6ef"; 745 | } 746 | .ti-layout-slider-alt:before { 747 | content: "\e6f0"; 748 | } 749 | .ti-layout-sidebar-right:before { 750 | content: "\e6f1"; 751 | } 752 | .ti-layout-sidebar-none:before { 753 | content: "\e6f2"; 754 | } 755 | .ti-layout-sidebar-left:before { 756 | content: "\e6f3"; 757 | } 758 | .ti-layout-placeholder:before { 759 | content: "\e6f4"; 760 | } 761 | .ti-layout-menu:before { 762 | content: "\e6f5"; 763 | } 764 | .ti-layout-menu-v:before { 765 | content: "\e6f6"; 766 | } 767 | .ti-layout-menu-separated:before { 768 | content: "\e6f7"; 769 | } 770 | .ti-layout-menu-full:before { 771 | content: "\e6f8"; 772 | } 773 | .ti-layout-media-right-alt:before { 774 | content: "\e6f9"; 775 | } 776 | .ti-layout-media-right:before { 777 | content: "\e6fa"; 778 | } 779 | .ti-layout-media-overlay:before { 780 | content: "\e6fb"; 781 | } 782 | .ti-layout-media-overlay-alt:before { 783 | content: "\e6fc"; 784 | } 785 | .ti-layout-media-overlay-alt-2:before { 786 | content: "\e6fd"; 787 | } 788 | .ti-layout-media-left-alt:before { 789 | content: "\e6fe"; 790 | } 791 | .ti-layout-media-left:before { 792 | content: "\e6ff"; 793 | } 794 | .ti-layout-media-center-alt:before { 795 | content: "\e700"; 796 | } 797 | .ti-layout-media-center:before { 798 | content: "\e701"; 799 | } 800 | .ti-layout-list-thumb:before { 801 | content: "\e702"; 802 | } 803 | .ti-layout-list-thumb-alt:before { 804 | content: "\e703"; 805 | } 806 | .ti-layout-list-post:before { 807 | content: "\e704"; 808 | } 809 | .ti-layout-list-large-image:before { 810 | content: "\e705"; 811 | } 812 | .ti-layout-line-solid:before { 813 | content: "\e706"; 814 | } 815 | .ti-layout-grid4:before { 816 | content: "\e707"; 817 | } 818 | .ti-layout-grid3:before { 819 | content: "\e708"; 820 | } 821 | .ti-layout-grid2:before { 822 | content: "\e709"; 823 | } 824 | .ti-layout-grid2-thumb:before { 825 | content: "\e70a"; 826 | } 827 | .ti-layout-cta-right:before { 828 | content: "\e70b"; 829 | } 830 | .ti-layout-cta-left:before { 831 | content: "\e70c"; 832 | } 833 | .ti-layout-cta-center:before { 834 | content: "\e70d"; 835 | } 836 | .ti-layout-cta-btn-right:before { 837 | content: "\e70e"; 838 | } 839 | .ti-layout-cta-btn-left:before { 840 | content: "\e70f"; 841 | } 842 | .ti-layout-column4:before { 843 | content: "\e710"; 844 | } 845 | .ti-layout-column3:before { 846 | content: "\e711"; 847 | } 848 | .ti-layout-column2:before { 849 | content: "\e712"; 850 | } 851 | .ti-layout-accordion-separated:before { 852 | content: "\e713"; 853 | } 854 | .ti-layout-accordion-merged:before { 855 | content: "\e714"; 856 | } 857 | .ti-layout-accordion-list:before { 858 | content: "\e715"; 859 | } 860 | .ti-ink-pen:before { 861 | content: "\e716"; 862 | } 863 | .ti-info-alt:before { 864 | content: "\e717"; 865 | } 866 | .ti-help-alt:before { 867 | content: "\e718"; 868 | } 869 | .ti-headphone-alt:before { 870 | content: "\e719"; 871 | } 872 | .ti-hand-point-up:before { 873 | content: "\e71a"; 874 | } 875 | .ti-hand-point-right:before { 876 | content: "\e71b"; 877 | } 878 | .ti-hand-point-left:before { 879 | content: "\e71c"; 880 | } 881 | .ti-hand-point-down:before { 882 | content: "\e71d"; 883 | } 884 | .ti-gallery:before { 885 | content: "\e71e"; 886 | } 887 | .ti-face-smile:before { 888 | content: "\e71f"; 889 | } 890 | .ti-face-sad:before { 891 | content: "\e720"; 892 | } 893 | .ti-credit-card:before { 894 | content: "\e721"; 895 | } 896 | .ti-control-skip-forward:before { 897 | content: "\e722"; 898 | } 899 | .ti-control-skip-backward:before { 900 | content: "\e723"; 901 | } 902 | .ti-control-record:before { 903 | content: "\e724"; 904 | } 905 | .ti-control-eject:before { 906 | content: "\e725"; 907 | } 908 | .ti-comments-smiley:before { 909 | content: "\e726"; 910 | } 911 | .ti-brush-alt:before { 912 | content: "\e727"; 913 | } 914 | .ti-youtube:before { 915 | content: "\e728"; 916 | } 917 | .ti-vimeo:before { 918 | content: "\e729"; 919 | } 920 | .ti-twitter:before { 921 | content: "\e72a"; 922 | } 923 | .ti-time:before { 924 | content: "\e72b"; 925 | } 926 | .ti-tumblr:before { 927 | content: "\e72c"; 928 | } 929 | .ti-skype:before { 930 | content: "\e72d"; 931 | } 932 | .ti-share:before { 933 | content: "\e72e"; 934 | } 935 | .ti-share-alt:before { 936 | content: "\e72f"; 937 | } 938 | .ti-rocket:before { 939 | content: "\e730"; 940 | } 941 | .ti-pinterest:before { 942 | content: "\e731"; 943 | } 944 | .ti-new-window:before { 945 | content: "\e732"; 946 | } 947 | .ti-microsoft:before { 948 | content: "\e733"; 949 | } 950 | .ti-list-ol:before { 951 | content: "\e734"; 952 | } 953 | .ti-linkedin:before { 954 | content: "\e735"; 955 | } 956 | .ti-layout-sidebar-2:before { 957 | content: "\e736"; 958 | } 959 | .ti-layout-grid4-alt:before { 960 | content: "\e737"; 961 | } 962 | .ti-layout-grid3-alt:before { 963 | content: "\e738"; 964 | } 965 | .ti-layout-grid2-alt:before { 966 | content: "\e739"; 967 | } 968 | .ti-layout-column4-alt:before { 969 | content: "\e73a"; 970 | } 971 | .ti-layout-column3-alt:before { 972 | content: "\e73b"; 973 | } 974 | .ti-layout-column2-alt:before { 975 | content: "\e73c"; 976 | } 977 | .ti-instagram:before { 978 | content: "\e73d"; 979 | } 980 | .ti-google:before { 981 | content: "\e73e"; 982 | } 983 | .ti-github:before { 984 | content: "\e73f"; 985 | } 986 | .ti-flickr:before { 987 | content: "\e740"; 988 | } 989 | .ti-facebook:before { 990 | content: "\e741"; 991 | } 992 | .ti-dropbox:before { 993 | content: "\e742"; 994 | } 995 | .ti-dribbble:before { 996 | content: "\e743"; 997 | } 998 | .ti-apple:before { 999 | content: "\e744"; 1000 | } 1001 | .ti-android:before { 1002 | content: "\e745"; 1003 | } 1004 | .ti-save:before { 1005 | content: "\e746"; 1006 | } 1007 | .ti-save-alt:before { 1008 | content: "\e747"; 1009 | } 1010 | .ti-yahoo:before { 1011 | content: "\e748"; 1012 | } 1013 | .ti-wordpress:before { 1014 | content: "\e749"; 1015 | } 1016 | .ti-vimeo-alt:before { 1017 | content: "\e74a"; 1018 | } 1019 | .ti-twitter-alt:before { 1020 | content: "\e74b"; 1021 | } 1022 | .ti-tumblr-alt:before { 1023 | content: "\e74c"; 1024 | } 1025 | .ti-trello:before { 1026 | content: "\e74d"; 1027 | } 1028 | .ti-stack-overflow:before { 1029 | content: "\e74e"; 1030 | } 1031 | .ti-soundcloud:before { 1032 | content: "\e74f"; 1033 | } 1034 | .ti-sharethis:before { 1035 | content: "\e750"; 1036 | } 1037 | .ti-sharethis-alt:before { 1038 | content: "\e751"; 1039 | } 1040 | .ti-reddit:before { 1041 | content: "\e752"; 1042 | } 1043 | .ti-pinterest-alt:before { 1044 | content: "\e753"; 1045 | } 1046 | .ti-microsoft-alt:before { 1047 | content: "\e754"; 1048 | } 1049 | .ti-linux:before { 1050 | content: "\e755"; 1051 | } 1052 | .ti-jsfiddle:before { 1053 | content: "\e756"; 1054 | } 1055 | .ti-joomla:before { 1056 | content: "\e757"; 1057 | } 1058 | .ti-html5:before { 1059 | content: "\e758"; 1060 | } 1061 | .ti-flickr-alt:before { 1062 | content: "\e759"; 1063 | } 1064 | .ti-email:before { 1065 | content: "\e75a"; 1066 | } 1067 | .ti-drupal:before { 1068 | content: "\e75b"; 1069 | } 1070 | .ti-dropbox-alt:before { 1071 | content: "\e75c"; 1072 | } 1073 | .ti-css3:before { 1074 | content: "\e75d"; 1075 | } 1076 | .ti-rss:before { 1077 | content: "\e75e"; 1078 | } 1079 | .ti-rss-alt:before { 1080 | content: "\e75f"; 1081 | } 1082 | -------------------------------------------------------------------------------- /public/fonts/themify.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/fonts/themify.eot -------------------------------------------------------------------------------- /public/fonts/themify.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/fonts/themify.ttf -------------------------------------------------------------------------------- /public/fonts/themify.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/fonts/themify.woff -------------------------------------------------------------------------------- /public/img/bootstrap-colorpicker/alpha-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/bootstrap-colorpicker/alpha-horizontal.png -------------------------------------------------------------------------------- /public/img/bootstrap-colorpicker/alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/bootstrap-colorpicker/alpha.png -------------------------------------------------------------------------------- /public/img/bootstrap-colorpicker/hue-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/bootstrap-colorpicker/hue-horizontal.png -------------------------------------------------------------------------------- /public/img/bootstrap-colorpicker/hue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/bootstrap-colorpicker/hue.png -------------------------------------------------------------------------------- /public/img/bootstrap-colorpicker/saturation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/bootstrap-colorpicker/saturation.png -------------------------------------------------------------------------------- /public/img/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/cover.jpg -------------------------------------------------------------------------------- /public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/favicon.png -------------------------------------------------------------------------------- /public/img/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/fork.png -------------------------------------------------------------------------------- /public/img/lightbox/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/lightbox/close.png -------------------------------------------------------------------------------- /public/img/lightbox/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/lightbox/loading.gif -------------------------------------------------------------------------------- /public/img/lightbox/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/lightbox/next.png -------------------------------------------------------------------------------- /public/img/lightbox/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/lightbox/prev.png -------------------------------------------------------------------------------- /public/img/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/logo-dark.png -------------------------------------------------------------------------------- /public/img/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/logo-light.png -------------------------------------------------------------------------------- /public/img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/preview.png -------------------------------------------------------------------------------- /public/img/screenshots/install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eladnava/applicationize/d73f5ffc1ee2da0403dda215be8c072c43ddfd1e/public/img/screenshots/install.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html lang="en"> 3 | <head> 4 | <!-- Page Encoding --> 5 | <meta charset="utf-8"> 6 | 7 | <!-- Page Title --> 8 | <title>Applicationize - Convert Web Apps to Desktop Apps 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | Fork me on GitHub 32 | 33 |
34 |
35 | 36 |
37 | 65 |
66 | 67 |
68 |
69 |
70 |
71 |

72 | We convert your favorite web apps into 73 |
desktop apps with their own dedicated launcher icon 74 |

75 |
76 |
77 | 78 |
79 | 80 |
81 | 82 |
83 |
84 |
85 | 86 |
87 | Preview 88 |

A preview of Facebook Messenger running as an applicationized app on Mac OS X.

89 |
90 | 91 |
92 |

Here's how it works

93 |

94 | Applicationize generates a Google Chrome extension that embeds your favorite web app and places a custom shortcut icon in your app launcher when you install it. 95 |

96 |

97 | Don't worry if that's all nonsense to you — it's dead simple to applicationize a web app: simply enter its URL, download a customized Google Chrome extension, install, and enjoy. 98 |

99 | Applicationize Me 100 |
101 |
102 | 103 |
104 | 105 |
106 | 107 |
108 |
109 |
110 | 111 |
112 |
113 | 114 |

Stay Organized

115 |

116 | Tabs are meant for websites, not web apps. Avoid clutter by applicationizing your frequently-used web apps. 117 |

118 |
119 |
120 | 121 |
122 |
123 | 124 |

Enhance Focus

125 |

126 | Avoid being distracted by time-wasting tabs in Google Chrome when you want to get things done. 127 |

128 |
129 |
130 | 131 |
132 |
133 | 134 |

Boost Productivity

135 |

136 | Access your web apps quickly and easily from your dock or taskbar, without having to open them in Chrome. 137 |

138 |
139 |
140 |
141 | 142 |
143 | 144 |
145 | 146 |
147 |
148 |
149 | 150 |
151 |

Go on, try it.

152 |

153 | Simply enter a URL address, download a customized Google Chrome extension, install, and enjoy. What are you waiting for? 154 |

155 | Applicationize Me 156 |
157 |
158 |
159 |
160 | 161 | 190 |
191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /public/js/applicationizer.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | "use strict"; 3 | 4 | // Color picker for customizing app frame color 5 | $('.colorpicker').colorpicker({ format: 'hex', align: 'left' }); 6 | 7 | // Handle advanced options toggle click 8 | $('.advanced-options-toggle').click(function (e) { 9 | // Cancel default browser navigation 10 | e.preventDefault(); 11 | 12 | // Make sure we're not currently generating an extension 13 | if (!$(this).find('.submit').is(':disabled')) { 14 | // Toggle the advanced options container's visibility 15 | $('.advanced-options-container').toggle(); 16 | } 17 | }); 18 | 19 | // Handle applicationize form submit 20 | $('.applicationize-form').submit(function (e) { 21 | // Get input URL 22 | var url = $(this).find('input[name="url"]').val(); 23 | var frameColor = $(this).find('input[name="frameColor"]').val(); 24 | 25 | // Try parsing the URL 26 | var parser = document.createElement('a'); 27 | parser.href = url; 28 | 29 | // Verify host & protocol 30 | if (!url || url.substring(0, 4) !== 'http' || !parser.host) { 31 | alert('Please provide a valid web app URL.\r\nExample: https://www.messenger.com/'); 32 | return e.preventDefault(); 33 | } 34 | 35 | // Verify valid frame color (if provided) 36 | if (frameColor && !/^#[0-9A-F]{6}$/i.test(frameColor)) { 37 | alert('Please provide a valid frame color hex value.'); 38 | return e.preventDefault(); 39 | } 40 | 41 | // Grab spinner and submit elements 42 | var submit = $(this).find('.submit'); 43 | var spinner = $(this).find('.spinner'); 44 | 45 | // Hide submit button and show spinner 46 | submit.hide(); 47 | spinner.show(); 48 | 49 | // Wait a few seconds (ugly hack since we don't know when the file has been downloaded) 50 | setTimeout(function () { 51 | // Flip the visibility back to normal 52 | spinner.hide(); 53 | submit.show(); 54 | }, 2500); 55 | }); 56 | }); -------------------------------------------------------------------------------- /public/js/bootstrap-colorpicker.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker v2.3.3 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | */ 5 | !function(a){"use strict";"object"==typeof exports?module.exports=a(window.jQuery):"function"==typeof define&&define.amd?define(["jquery"],a):window.jQuery&&!window.jQuery.fn.colorpicker&&a(window.jQuery)}(function(a){"use strict";var b=function(b,c){this.value={h:0,s:0,b:0,a:1},this.origFormat=null,c&&a.extend(this.colors,c),b&&(void 0!==b.toLowerCase?(b+="",this.setColor(b)):void 0!==b.h&&(this.value=b))};b.prototype={constructor:b,colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",transparent:"transparent"},_sanitizeNumber:function(a){return"number"==typeof a?a:isNaN(a)||null===a||""===a||void 0===a?1:""===a?0:void 0!==a.toLowerCase?(a.match(/^\./)&&(a="0"+a),Math.ceil(100*parseFloat(a))/100):1},isTransparent:function(a){return a?(a=a.toLowerCase().trim(),"transparent"===a||a.match(/#?00000000/)||a.match(/(rgba|hsla)\(0,0,0,0?\.?0\)/)):!1},rgbaIsTransparent:function(a){return 0===a.r&&0===a.g&&0===a.b&&0===a.a},setColor:function(a){a=a.toLowerCase().trim(),a&&(this.isTransparent(a)?this.value={h:0,s:0,b:0,a:0}:this.value=this.stringToHSB(a)||{h:0,s:0,b:0,a:1})},stringToHSB:function(b){b=b.toLowerCase();var c;"undefined"!=typeof this.colors[b]&&(b=this.colors[b],c="alias");var d=this,e=!1;return a.each(this.stringParsers,function(a,f){var g=f.re.exec(b),h=g&&f.parse.apply(d,[g]),i=c||f.format||"rgba";return h?(e=i.match(/hsla?/)?d.RGBtoHSB.apply(d,d.HSLtoRGB.apply(d,h)):d.RGBtoHSB.apply(d,h),d.origFormat=i,!1):!0}),e},setHue:function(a){this.value.h=1-a},setSaturation:function(a){this.value.s=a},setBrightness:function(a){this.value.b=1-a},setAlpha:function(a){this.value.a=Math.round(parseInt(100*(1-a),10)/100*100)/100},toRGB:function(a,b,c,d){a||(a=this.value.h,b=this.value.s,c=this.value.b),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Math.abs(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],{r:Math.round(255*e),g:Math.round(255*f),b:Math.round(255*g),a:d||this.value.a}},toHex:function(a,b,c,d){var e=this.toRGB(a,b,c,d);return this.rgbaIsTransparent(e)?"transparent":"#"+(1<<24|parseInt(e.r)<<16|parseInt(e.g)<<8|parseInt(e.b)).toString(16).substr(1)},toHSL:function(a,b,c,d){a=a||this.value.h,b=b||this.value.s,c=c||this.value.b,d=d||this.value.a;var e=a,f=(2-b)*c,g=b*c;return g/=f>0&&1>=f?f:2-f,f/=2,g>1&&(g=1),{h:isNaN(e)?0:e,s:isNaN(g)?0:g,l:isNaN(f)?0:f,a:isNaN(d)?0:d}},toAlias:function(a,b,c,d){var e=this.toHex(a,b,c,d);for(var f in this.colors)if(this.colors[f]===e)return f;return!1},RGBtoHSB:function(a,b,c,d){a/=255,b/=255,c/=255;var e,f,g,h;return g=Math.max(a,b,c),h=g-Math.min(a,b,c),e=0===h?null:g===a?(b-c)/h:g===b?(c-a)/h+2:(a-b)/h+4,e=(e+360)%6*60/360,f=0===h?0:h/g,{h:this._sanitizeNumber(e),s:f,b:g,a:this._sanitizeNumber(d)}},HueToRGB:function(a,b,c){return 0>c?c+=1:c>1&&(c-=1),1>6*c?a+(b-a)*c*6:1>2*c?b:2>3*c?a+(b-a)*(2/3-c)*6:a},HSLtoRGB:function(a,b,c,d){0>b&&(b=0);var e;e=.5>=c?c*(1+b):c+b-c*b;var f=2*c-e,g=a+1/3,h=a,i=a-1/3,j=Math.round(255*this.HueToRGB(f,e,g)),k=Math.round(255*this.HueToRGB(f,e,h)),l=Math.round(255*this.HueToRGB(f,e,i));return[j,k,l,this._sanitizeNumber(d)]},toString:function(a){a=a||"rgba";var b=!1;switch(a){case"rgb":return b=this.toRGB(),this.rgbaIsTransparent(b)?"transparent":"rgb("+b.r+","+b.g+","+b.b+")";case"rgba":return b=this.toRGB(),"rgba("+b.r+","+b.g+","+b.b+","+b.a+")";case"hsl":return b=this.toHSL(),"hsl("+Math.round(360*b.h)+","+Math.round(100*b.s)+"%,"+Math.round(100*b.l)+"%)";case"hsla":return b=this.toHSL(),"hsla("+Math.round(360*b.h)+","+Math.round(100*b.s)+"%,"+Math.round(100*b.l)+"%,"+b.a+")";case"hex":return this.toHex();case"alias":return this.toAlias()||this.toHex();default:return b}},stringParsers:[{re:/rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/,format:"rgb",parse:function(a){return[a[1],a[2],a[3],1]}},{re:/rgb\(\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*?\)/,format:"rgb",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],1]}},{re:/rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[a[1],a[2],a[3],a[4]]}},{re:/rgba\(\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],a[4]]}},{re:/hsl\(\s*(\d*(?:\.\d+)?)\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*?\)/,format:"hsl",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/hsla\(\s*(\d*(?:\.\d+)?)\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"hsla",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/#?([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,format:"hex",parse:function(a){return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16),1]}},{re:/#?([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,format:"hex",parse:function(a){return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16),1]}}],colorNameToHex:function(a){return"undefined"!=typeof this.colors[a.toLowerCase()]?this.colors[a.toLowerCase()]:!1}};var c={horizontal:!1,inline:!1,color:!1,format:!1,input:"input",container:!1,component:".add-on, .input-group-addon",sliders:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setHue"},alpha:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setAlpha"}},slidersHorz:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:100,maxTop:0,callLeft:"setHue",callTop:!1},alpha:{maxLeft:100,maxTop:0,callLeft:"setAlpha",callTop:!1}},template:'