├── .gitignore ├── images ├── off16.png ├── off32.png ├── off48.png ├── bloom16.png ├── bloom32.png ├── bloom48.png ├── off128.png └── bloom128.png ├── media └── flowers.png ├── .vscode └── settings.json ├── style.css ├── manifest.json ├── LICENSE ├── insert_style.css ├── scripts ├── data_old.json ├── data.json ├── background.js ├── bloom.js └── jquery-3.5.0.slim.min.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /images/off16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/off16.png -------------------------------------------------------------------------------- /images/off32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/off32.png -------------------------------------------------------------------------------- /images/off48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/off48.png -------------------------------------------------------------------------------- /images/bloom16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/bloom16.png -------------------------------------------------------------------------------- /images/bloom32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/bloom32.png -------------------------------------------------------------------------------- /images/bloom48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/bloom48.png -------------------------------------------------------------------------------- /images/off128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/off128.png -------------------------------------------------------------------------------- /media/flowers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/media/flowers.png -------------------------------------------------------------------------------- /images/bloom128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peilingjiang/cyberflowers/HEAD/images/bloom128.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "cyberflowers", 4 | "cyberflower", 5 | "jiang", 6 | "peiling", 7 | ] 8 | } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | pointer-events: none; 3 | font-family: 'Times New Roman', Times, serif; 4 | padding: 0; 5 | margin: 0; 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | z-index: 999998; 10 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cyberflowers", 3 | "version": "0.3.6", 4 | "description": "People write, people seed.", 5 | "permissions": [ 6 | "activeTab", 7 | "tabs" 8 | ], 9 | "background": { 10 | "scripts": ["scripts/background.js"], 11 | "persistent": false 12 | }, 13 | "browser_action": { 14 | "default_icon": { 15 | "16": "images/off16.png", 16 | "32": "images/off32.png", 17 | "48": "images/off48.png", 18 | "128": "images/off128.png" 19 | }, 20 | "default_title": "Cyberflowers" 21 | }, 22 | "content_scripts": [{ 23 | "matches": [""], 24 | "all_frames": true, 25 | "css": ["style.css"] 26 | }], 27 | "web_accessible_resources": [ 28 | "scripts/data.json" 29 | ], 30 | "icons": { 31 | "16": "images/bloom16.png", 32 | "32": "images/bloom32.png", 33 | "48": "images/bloom48.png", 34 | "128": "images/bloom128.png" 35 | }, 36 | "manifest_version": 2 37 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Peiling Jiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /insert_style.css: -------------------------------------------------------------------------------- 1 | /* a { 2 | pointer-events: none !important 3 | } */ 4 | 5 | ::selection { 6 | background: none !important; 7 | color: #dddddd !important; 8 | } 9 | 10 | #saveButtonCyberFlowers { 11 | position: fixed !important; 12 | right: 17px !important; 13 | top: 17px !important; 14 | margin: 0 !important; 15 | padding: 10px !important; 16 | overflow: hidden !important; 17 | background: #000000 !important; 18 | opacity: 0.85 !important; 19 | color: #ffffff !important; 20 | border: none !important; 21 | box-shadow: none !important; 22 | outline: none !important; 23 | border-radius: 4px !important; 24 | border-color: transparent !important; 25 | font-size: 12px !important; 26 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important; 27 | font-weight: 300 !important; 28 | z-index: 999999 !important; 29 | cursor: grab !important; 30 | } 31 | 32 | #saveButtonCyberFlowers:hover { 33 | background: #333333 !important; 34 | cursor: grab !important; 35 | } 36 | 37 | #saveButtonCyberFlowers:active { 38 | background: #000000 !important; 39 | cursor: grabbing !important; 40 | } -------------------------------------------------------------------------------- /scripts/data_old.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "OK", 3 | 4 | "color": { 5 | "0": [[ 17, 39, 51], [ 58, 0, 24], [ 76, 32, 0]], 6 | "1": [[ 23, 58, 73], [ 71, 29, 45], [102, 55, 16]], 7 | "2": [[ 26, 73, 89], [ 86, 29, 48], [119, 69, 24]], 8 | "3": [[ 31, 92, 109], [104, 29, 51], [142, 88, 35]], 9 | "4": [[ 32, 132, 130], [107, 29, 68], [168, 91, 39]], 10 | "5": [[ 31, 131, 147], [135, 24, 75], [183, 95, 35]], 11 | "6": [[ 29, 161, 168], [168, 26, 84], [211, 112, 45]], 12 | "7": [[ 25, 188, 188], [198, 36, 98], [229, 120, 40]], 13 | "8": [[ 23, 192, 201], [219, 55, 102], [242, 125, 29]], 14 | "9": [[ 0, 207, 229], [255, 65, 95], [255, 134, 20]] 15 | }, 16 | 17 | "en": { 18 | "A": [[0, -1], 11, 4], 19 | "a": [[0, -1], 11, 4], 20 | "B": [[0, -1.6], 11, 3.5], 21 | "b": [[0, 3], 12, 3.5], 22 | "C": [[0.7, 0], 12, 4], 23 | "c": [[0.65, 0], 12, 4], 24 | "D": [[0.5, -1.7], 11, 4], 25 | "d": [[0.2, -0.7], 12, 4], 26 | "E": [[0, -1.6], 15, 4], 27 | "e": [[0, -0.6], 12, 6], 28 | "F": [[0, -1.6], 15, 4], 29 | "f": [[0.5, -0.6], 14, 4], 30 | "G": [[-0.1, 0.1], 11, 4.5], 31 | "g": [[-0.2, -1.5], 11, 4], 32 | "H": [[1.8, -0.3], 11, 4], 33 | "h": [[1.3, -1], 13, 4.5], 34 | "I": [[1.7, -0.3], 14, 4], 35 | "i": [[0.6, 3.3], 12, 4.5], 36 | "J": [[1, 0.2], 12, 4.5], 37 | "j": [[0.7, 3.3], 15, 4], 38 | "K": [[-1.3, -2.3], 15, 4], 39 | "k": [[1.7, 3.2], 14, 4.5], 40 | "L": [[0.6, 3.1], 12, 4], 41 | "l": [[-2.7, -0.3], 16, 4], 42 | "M": [[-1.8, -0.4], 10, 4.5], 43 | "m": [[0.2, -0.9], 12, 4], 44 | "N": [[2.1, 0.6], 12, 4], 45 | "n": [[0.3, -0.4], 11, 5], 46 | "O": [[-0.6, 0], 11, 4.5], 47 | "o": [[0, -0.7], 12, 4], 48 | "P": [[0.9, -0.5], 13, 4], 49 | "p": [[0.7, -1.7], 12, 4], 50 | "Q": [[-3, -1], 11, 4], 51 | "q": [[-2.1, -2], 12, 4], 52 | "R": [[-0.7, -1.3], 12, 4], 53 | "r": [[0.2, -0.8], 14, 4.5], 54 | "S": [[1.7, 0.3], 13, 4], 55 | "s": [[-2.4, -0.7], 12, 5], 56 | "T": [[0.1, -0.6], 13, 4], 57 | "t": [[-1.7, -0.4], 13, 4.5], 58 | "U": [[0.3, 4], 13, 4], 59 | "u": [[0.4, -1.6], 14, 4], 60 | "V": [[0.4, 3.6], 13, 4], 61 | "v": [[-1.5, 3.3], 12, 4.5], 62 | "W": [[0.2, -1], 11, 4], 63 | "w": [[-3.5, -0.6], 13, 4], 64 | "X": [[0.8, 0.7], 11, 4], 65 | "x": [[0.4, 2.5], 12, 4], 66 | "Y": [[0, -0.9, 12, 4]], 67 | "y": [[0.6, -1.2], 12, 4], 68 | "Z": [[-0.3, 3.4], 12, 4], 69 | "z": [[0.5, -0.9], 13, 4] 70 | } 71 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cyberflowers 2 | 3 | ![Flowers](./media/flowers.png) 4 | 5 | Every word is a seed of an idea. And seeds of thoughts with distinct emotion, arousal, attitude, and meaning would grow into different flowers of affects (n. emotion or desire, especially as influencing behavior or action). I built this Chrome extension with my Cyberflowers text-based art to help people find positive or negative sentiments on the web (for fun), with the help of ml5.js sentiment library. Please read my [documentation](https://blog.jpl.design/posts/s20/pixel-by-pixel/cyberflowers/) for more about this project. 6 | 7 | The project was developed for **The World: Pixel by Pixel** course (NYU ITP, Spring 2020) under instructions of Daniel Rozin, during the quarantine in COVID-19 pandemic. 8 | 9 | ## How to use it? 10 | 11 | 0. Install Google Chrome. 12 | 1. Download this repository. 13 | 2. Go to `chrome://extensions` in your browser. 14 | 3. Turn on *Developer mode*. 15 | 4. Choose *Load unpacked* and load the folder. 16 | 5. Now you should see a cyberflower icon next to the address bar. You can go to any website with some text in it, click on the icon and the extension will start running. 17 | 6. You can either click on the character and sow a new cyberflower rooted on it, or select a section of text and move your cursor over it to quickly sow many. After that, you could also click on "Pick Cyberflowers" to save the picture of the flowers. 18 | 19 | Please notice that although the extension requires `tabs` and `activeTab` permissions (there will be no pop-ups as everything happens in the background), the extension just uses them for the id of each tab and finding the active one. 20 | 21 | ## Technical References 22 | 23 | 1. [Determine the position index of a character within an HTML element when clicked - Stack Overflow](https://stackoverflow.com/questions/8105824/determine-the-position-index-of-a-character-within-an-html-element-when-clicked) 24 | 2. [Get Position of text inside a HTML element - Stack Overflow](https://stackoverflow.com/questions/30061542/get-position-of-text-inside-a-html-element) 25 | 3. [Sentiment - ml5.js](https://learn.ml5js.org/docs/#/reference/sentiment) 26 | 4. [Getting Started Tutorial - Chrome Extension](https://developer.chrome.com/extensions/getstarted) 27 | 28 | ## Links 29 | 30 | 1. [GitHub Repository](https://github.com/peilingjiang/ima-courses/tree/master/s20-pixel/final_cyberflowers) for the example p5.js sketches 31 | 2. [Embedded p5.js sketch](https://editor.p5js.org/peilingjiang/present/b6SH4VwL1) (Matrix) 32 | 3. [Embedded p5.js sketch](https://editor.p5js.org/peilingjiang/present/ixM4XnkKR) (Formation) 33 | 4. [Embedded p5.js sketch](https://editor.p5js.org/peilingjiang/present/hFP05DMUk) (Composition) 34 | 5. [Embedded p5.js sketch](https://editor.p5js.org/peilingjiang/present/-CYsUUWxC) (Groups) 35 | -------------------------------------------------------------------------------- /scripts/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "OK", 3 | 4 | "color": { 5 | "negative": [[ 17, 39, 51], [ 58, 0, 24], [ 76, 32, 0]], 6 | "positive": [[ 0, 207, 229], [255, 65, 95], [255, 134, 20]] 7 | }, 8 | 9 | "en": { 10 | "A": [[0, -1], 11, 4], 11 | "a": [[0, -1], 11, 4], 12 | "B": [[0, -1.6], 11, 3.5], 13 | "b": [[0, 3], 12, 3.5], 14 | "C": [[0.7, 0], 12, 4], 15 | "c": [[0.65, 0], 12, 4], 16 | "D": [[0.5, -1.7], 11, 4], 17 | "d": [[0.2, -0.7], 12, 4], 18 | "E": [[0, -1.6], 15, 4], 19 | "e": [[0, -0.6], 12, 6], 20 | "F": [[0, -1.6], 15, 4], 21 | "f": [[0.5, -0.6], 14, 4], 22 | "G": [[-0.1, 0.1], 11, 4.5], 23 | "g": [[-0.2, -1.5], 11, 4], 24 | "H": [[1.8, -0.3], 11, 4], 25 | "h": [[1.3, -1], 13, 4.5], 26 | "I": [[1.7, -0.3], 14, 4], 27 | "i": [[0.6, 3.3], 12, 4.5], 28 | "J": [[1, 0.2], 12, 4.5], 29 | "j": [[0.7, 3.3], 15, 4], 30 | "K": [[-1.3, -2.3], 15, 4], 31 | "k": [[1.7, 3.2], 14, 4.5], 32 | "L": [[0.6, 3.1], 12, 4], 33 | "l": [[-2.7, -0.3], 16, 4], 34 | "M": [[-1.8, -0.4], 10, 4.5], 35 | "m": [[0.2, -0.9], 12, 4], 36 | "N": [[2.1, 0.6], 12, 4], 37 | "n": [[0.3, -0.4], 11, 5], 38 | "O": [[-0.6, 0], 11, 4.5], 39 | "o": [[0, -0.7], 12, 4], 40 | "P": [[0.9, -0.5], 13, 4], 41 | "p": [[0.7, -1.7], 12, 4], 42 | "Q": [[-3, -1], 11, 4], 43 | "q": [[-2.1, -2], 12, 4], 44 | "R": [[-0.7, -1.3], 12, 4], 45 | "r": [[0.2, -0.8], 14, 4.5], 46 | "S": [[1.7, 0.3], 13, 4], 47 | "s": [[-2.4, -0.7], 12, 5], 48 | "T": [[0.1, -0.6], 13, 4], 49 | "t": [[-1.7, -0.4], 13, 4.5], 50 | "U": [[0.3, 4], 13, 4], 51 | "u": [[0.4, -1.6], 14, 4], 52 | "V": [[0.4, 3.6], 13, 4], 53 | "v": [[-1.5, 3.3], 12, 4.5], 54 | "W": [[0.2, -1], 11, 4], 55 | "w": [[-3.5, -0.6], 13, 4], 56 | "X": [[0.8, 0.7], 11, 4], 57 | "x": [[0.4, 2.5], 12, 4], 58 | "Y": [[0, -0.9], 12, 4], 59 | "y": [[0.6, -1.2], 12, 4], 60 | "Z": [[-0.3, 3.4], 12, 4], 61 | "z": [[0.5, -0.9], 13, 4], 62 | "0": [[1.3, 0.2], 12, 4], 63 | "1": [[-0.4, -0.5], 11, 4], 64 | "2": [[1.4, 0], 12, 3.5], 65 | "3": [[0.3, -0.5], 11, 4], 66 | "4": [[1.4, -0.5], 13, 4], 67 | "5": [[-2.6, -0.1], 12, 4], 68 | "6": [[1.4, 1], 12, 4], 69 | "7": [[1, 0.4], 14, 4], 70 | "8": [[1.5, 0.5], 10, 4], 71 | "9": [[0.6, 0.1], 11, 4], 72 | ".": [[0.5, -0.1], 12, 4], 73 | ",": [[1.3, 0.3], 13, 4.5], 74 | "!": [[0.7, -0.2], 12, 4], 75 | "?": [[0.4, -0.1], 12, 4], 76 | "@": [[0.2, 2], 10, 3.5], 77 | "#": [[1.4, 0.7], 12, 4], 78 | "%": [[0.8, 0.5], 12, 4], 79 | "^": [[0, 0], 15, 4], 80 | "&": [[0.3, -0.2], 11, 4], 81 | "*": [[-0.5, 0], 12, 4] 82 | } 83 | } -------------------------------------------------------------------------------- /scripts/background.js: -------------------------------------------------------------------------------- 1 | let logMsg = { 2 | 0: "--- 0 background running ---", 3 | "jquery": "--- j jquery-3.5.0 load ----", 4 | "p5": "--- p p5.js load -----------", 5 | "ml5": "--- m ml5.js load ----------", 6 | "bloom": "--- b bloom.js load --------", 7 | "lang": "--- l language send --------" 8 | } 9 | 10 | console.log(logMsg[0]); 11 | 12 | let toggle = {}; // tabID: true/false 13 | 14 | /* New tab created */ 15 | chrome.tabs.onCreated.addListener((tab) => { 16 | toggle[tab.id] = false; 17 | return false; 18 | }); 19 | /* Old tab removed */ 20 | chrome.tabs.onRemoved.addListener((tab) => { 21 | if (tab.id in toggle) 22 | delete toggle[tab.id]; 23 | return false; 24 | }); 25 | 26 | /* --- Switch tabs --- */ 27 | chrome.tabs.onActivated.addListener((activeInfo) => { 28 | // Tab already opened -- debug -- 29 | if (!(activeInfo.tabId in toggle)) 30 | toggle[activeInfo.tabId] = false; 31 | 32 | TabSetIcon(activeInfo.tabId); 33 | return false; 34 | }); 35 | 36 | /* --- Update tabs --- */ 37 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 38 | toggle[tabId] = false; 39 | TabSetIcon(tabId); 40 | return false; 41 | }); 42 | 43 | chrome.browserAction.onClicked.addListener((tab) => { 44 | // Tab already opened -- debug -- 45 | if (!(tab.id in toggle)) 46 | toggle[tab.id] = false; 47 | 48 | toggle[tab.id] = !toggle[tab.id]; 49 | 50 | if (toggle[tab.id]) { 51 | // ON 52 | TabSetIcon(tab.id); 53 | 54 | chrome.tabs.executeScript(tab.id, { 55 | file: "scripts/jquery-3.5.0.slim.min.js" 56 | }); 57 | console.log(logMsg.jquery); 58 | 59 | chrome.tabs.insertCSS(tab.id, { 60 | file: "insert_style.css" 61 | }); 62 | chrome.tabs.executeScript(tab.id, { 63 | file: "scripts/p5.min.js" 64 | }, (r) => { 65 | console.log(logMsg.p5); 66 | chrome.tabs.executeScript(tab.id, { 67 | file: "scripts/ml5.min.js" 68 | }, (r) => { 69 | console.log(logMsg.ml5); 70 | chrome.tabs.executeScript(tab.id, { 71 | file: "scripts/bloom.js" 72 | }, (r) => { 73 | console.log(logMsg.bloom); 74 | chrome.tabs.detectLanguage(tab.id, (lang) => { 75 | chrome.tabs.sendMessage(tab.id, { 76 | task: "language", 77 | id: tab.id, 78 | data: lang 79 | }); 80 | console.log(logMsg.lang); 81 | }); 82 | }); 83 | }); 84 | }); 85 | } else if (!toggle[tab.id]) { 86 | // OFF 87 | TabSetIcon(tab.id); 88 | // TODO: a more elegant way to remove canvas and restore page 89 | chrome.tabs.reload(tab.id); 90 | } 91 | return false; 92 | }); 93 | 94 | let TabSetIcon = (id) => { 95 | // TODO: better ways to toggle icons 96 | if (toggle[id]) { 97 | // ON 98 | chrome.browserAction.setIcon({ 99 | path: { 100 | "16": "images/bloom16.png", 101 | "32": "images/bloom32.png", 102 | "48": "images/bloom48.png", 103 | "128": "images/bloom128.png" 104 | }, 105 | tabId: id 106 | }); 107 | } else if (!toggle[id]) { 108 | // OFF 109 | chrome.browserAction.setIcon({ 110 | path: { 111 | "16": "images/off16.png", 112 | "32": "images/off32.png", 113 | "48": "images/off48.png", 114 | "128": "images/off128.png" 115 | }, 116 | tabId: id 117 | }); 118 | } 119 | }; -------------------------------------------------------------------------------- /scripts/bloom.js: -------------------------------------------------------------------------------- 1 | /* 2 | Cyberflowers 3 | Peiling Jiang 4 | 2020 5 | Pixel by Pixel Daniel Rozin 6 | */ 7 | 8 | const debug = false 9 | 10 | let bloomLogMsg = { 11 | d: "=== d data ===============", 12 | 0: "--- 0 language learned ---", 13 | 1: "--- 1 sketch preload -----", 14 | 2: "--- 2 sketch setup -------", 15 | 3: "--- 3 sentiment ready ----", 16 | c: "___ c CLICKED ____________", 17 | h: "___ h HOVERED ____________" 18 | }; 19 | 20 | /* External libraries: jQuery, p5, ml5 */ 21 | 22 | // en 23 | // let ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); 24 | 25 | let pageLang; // Language of the page 26 | 27 | let sketch = (s) => { 28 | // Parse data from data.json 29 | let data; 30 | let dataReady = false; 31 | fetch(chrome.extension.getURL("scripts/data.json")) 32 | .then((response) => response.json()) 33 | .then((json) => { 34 | data = json; 35 | dataReady = true; 36 | if (debug) console.log(bloomLogMsg.d); 37 | }); 38 | 39 | // let colorChoices = s.random(findChoices([0, 1, 2])); /* Two colors */ 40 | let colorChoices = [s.random([0, 1, 2])]; /* One color */ 41 | 42 | let c; // Canvas 43 | let frameRate = 30; 44 | let scale = 1; // Scale of drawings on canvas 45 | 46 | let flowers = []; // Array of all flowers 47 | let flowersNum = 0; 48 | 49 | let saoMode = false; /* 扫模式 */ 50 | let lastMouseX = 0; 51 | let lastMouseY = 0; 52 | let mouseDistBuffer = 80; // Avoid flowers being next to each other 53 | let rectList; 54 | let selectionInfo = {}; // Char index info of rects 55 | 56 | let saveButton; 57 | let saveCount = 0; 58 | 59 | let sentimentReady = false; 60 | const sentiment = ml5.sentiment("movieReviews", () => { 61 | sentimentReady = true; 62 | if (debug) console.log(bloomLogMsg[3]); 63 | }); 64 | 65 | s.setFont = () => { 66 | switch (pageLang) { 67 | case "en": 68 | s.textFont("Times"); 69 | s.textStyle(s.NORMAL); 70 | break; 71 | } 72 | }; 73 | 74 | // let clicked = false; 75 | let dragged = false; 76 | 77 | s.setup = () => { 78 | // Add canvas for the whole page 79 | c = s.createCanvas( 80 | Math.min(document.documentElement.scrollWidth, 1700), 81 | Math.min(document.documentElement.scrollHeight, 1700) 82 | ); 83 | c.position(0, 0); 84 | s.frameRate(frameRate); 85 | s.angleMode(s.DEGREES); 86 | 87 | if (debug) console.log(bloomLogMsg[2]); 88 | 89 | /* create saveButton */ 90 | // TODO: Also save the original page as background (maybe) 91 | saveButton = s.createButton("Pick Cyberflowers"); 92 | saveButton.id("saveButtonCyberFlowers"); 93 | saveButton.mousePressed(saveIt); 94 | }; 95 | 96 | s.draw = () => { 97 | s.clear(); 98 | for (let i = 0; i < flowersNum; i++) { 99 | flowers[i].bloom(); 100 | } 101 | }; 102 | 103 | s.windowResized = () => { 104 | s.resizeCanvas( 105 | document.documentElement.scrollWidth, 106 | document.documentElement.scrollHeight 107 | ); 108 | }; 109 | 110 | s.mouseWheel = () => { 111 | if (saoMode) { 112 | // Rebuild rectList 113 | // As rects have relative x and y locations compared to the actual scroll position 114 | rectList = window.getSelection().getRangeAt(0).getClientRects(); 115 | } 116 | }; 117 | 118 | s.mouseClicked = () => { 119 | if (!dragged && saoMode) { 120 | setTimeout(() => { 121 | saoMode = false; 122 | selectionInfo = {}; 123 | }, 100); 124 | } 125 | return false; 126 | }; 127 | 128 | s.mouseDragged = () => { 129 | dragged = true; 130 | }; 131 | 132 | s.mouseReleased = () => { 133 | setTimeout(() => { 134 | // clicked = false; 135 | dragged = false; 136 | }, 300); 137 | if (dragged && validSelection()) { 138 | /* saoMode assumes that all chars have equal length */ 139 | saoMode = true; 140 | 141 | let selection = window.getSelection(); // Local 142 | // Get rectList of selection rects 143 | rectList = window.getSelection().getRangeAt(0).getClientRects(); 144 | let totalWidth = 0; 145 | for (let i = 0; i < rectList.length; i++) 146 | totalWidth += rectList[i].width; 147 | 148 | let tempStart = 0; 149 | let startOffset = s.min(selection.focusOffset, selection.anchorOffset); 150 | let endOffset = s.max(selection.focusOffset, selection.anchorOffset); 151 | for (let i = 0; i < rectList.length; i++) { 152 | // Build info list of starting and end offsets of each block 153 | selectionInfo[i] = [ 154 | s.floor(s.map(tempStart, 0, totalWidth, startOffset, endOffset)), /* Start offset */ 155 | s.floor(s.map(tempStart + rectList[i].width, 0, totalWidth, startOffset, endOffset)) /* End offset */ 156 | ]; 157 | tempStart += rectList[i].width; 158 | } 159 | } 160 | }; 161 | 162 | let pageX = 0; // Relative mouseX compared to the current window view 163 | let pageY = 0; // Relative mouseY compared to the current window view 164 | 165 | s.mouseMoved = () => { 166 | if (saoMode) { 167 | pageX = s.mouseX - $(window).scrollLeft(); 168 | pageY = s.mouseY - $(window).scrollTop(); 169 | if ((s.dist(pageX, pageY, lastMouseX, lastMouseY) > mouseDistBuffer) && rectList.length) { 170 | sowFlowerHover(); 171 | } 172 | } 173 | }; 174 | 175 | let sowFlowerHover = () => { 176 | if ((pageLang in data) && dataReady && sentimentReady) { 177 | for (let i = 0; i < rectList.length; i++) { 178 | if ( 179 | /* If the cursor is in the selection area of a **line** */ 180 | pageX >= rectList[i].left && pageX <= rectList[i].right && 181 | pageY >= rectList[i].top && pageY <= rectList[i].bottom 182 | ) { 183 | // Index of the char hovered on 184 | let hoverInd = s.map( 185 | pageX - rectList[i].left, 186 | 0, rectList[i].width, 187 | selectionInfo[i][0], selectionInfo[i][1] /* The starting and ending points of THIS block */ 188 | ); 189 | let text = window.getSelection().focusNode.wholeText; 190 | if (text != undefined && text.charAt((hoverInd >> 1) << 1) in data[pageLang]) { 191 | if (debug) console.log(bloomLogMsg.h); 192 | flowers.push(new Flower(s.mouseX, s.mouseY, pageLang, text.charAt((hoverInd >> 1) << 1), getSentimentScore(text, hoverInd))); 193 | flowersNum++; 194 | lastMouseX = pageX; 195 | lastMouseY = pageY; 196 | } else { 197 | return false; 198 | } 199 | break; 200 | } 201 | } 202 | } 203 | }; 204 | 205 | // When clicked on text elements... 206 | $("p, textarea, span, h1, h2, h3, h4, h5, h6, q, cite, blockquote, a, em, i, b, strong").click(() => { 207 | // clicked = true; 208 | if (!dragged && !saoMode) 209 | sowFlowerClicked(s.mouseX, s.mouseY); 210 | return false; 211 | }); 212 | 213 | let sowFlowerClicked = (x, y) => { 214 | /* Must be clicked by a real cursor */ 215 | if ((pageLang in data) && dataReady && sentimentReady) { 216 | let selection = window.getSelection(); 217 | let charIndex = selection.focusOffset; 218 | let text = selection.focusNode.wholeText; 219 | 220 | if (text.charAt((charIndex >> 1) << 1) in data[pageLang]) { 221 | let sentimentScore = getSentimentScore(text, charIndex); 222 | // Sow seed for new flower 223 | if (debug) console.log(bloomLogMsg.c); 224 | flowers.push(new Flower(x, y, pageLang, text.charAt((charIndex >> 1) << 1), sentimentScore)); 225 | flowersNum++; 226 | } 227 | } 228 | }; 229 | 230 | let getSentimentScore = (t, ind) => { 231 | /* Truncate the text to 70 chars at most */ 232 | return sentiment.predict(t.slice(s.constrain(ind - 35, 0, ind), s.constrain(ind + 35, ind, t.length))).score; 233 | }; 234 | 235 | class Flower { 236 | constructor(x, y, lang, char, sentiment) { 237 | /* 238 | lang: language 239 | char: the root character 240 | sentiment: [0, 1] sentiment of text around 241 | */ 242 | 243 | this.x = x; 244 | this.y = y; 245 | this.lang = lang; 246 | this.char = char; 247 | this.sentiment = sentiment; 248 | 249 | this.t = 0; // For bloom animation 250 | this.bloomTime = s.int(0.3 * frameRate * s.map(sentiment, 0, 1, 1.2, 0.8) + s.random(-frameRate / 6, frameRate / 6)); // Total bloom time base 1 sec 251 | this.progress = 0; 252 | 253 | /* [offset, angle (in times), height] */ 254 | this.offset = data[lang][char][0]; // Offset of char in flower 255 | this.angle = data[lang][char][1]; // How many times rotated! 256 | this.height = data[lang][char][2]; // Height in standard size 257 | 258 | /* Different sentiment mapped range from tune file */ 259 | this.radiusScale = scale * s.map(s.abs(sentiment - 0.5), 0, 0.5, 5, 9); // Size of flower 260 | 261 | this.baseColorSet; 262 | this.buildBaseColorSet(); 263 | } 264 | 265 | buildBaseColorSet() { 266 | let thisChoice = s.random(colorChoices); 267 | let baseColor = [ 268 | s.map(this.sentiment, 0, 1, data.color.negative[thisChoice][0], data.color.positive[thisChoice][0]), 269 | s.map(this.sentiment, 0, 1, data.color.negative[thisChoice][1], data.color.positive[thisChoice][1]), 270 | s.map(this.sentiment, 0, 1, data.color.negative[thisChoice][2], data.color.positive[thisChoice][2]) 271 | ]; 272 | this.baseColorSet = []; 273 | // Each cyber-petal will have a unique color 274 | for (let i = 0; i < this.angle; i++) 275 | this.baseColorSet.push([ 276 | s.constrain(baseColor[0] + s.random(-17, 17), 0, 255), 277 | s.constrain(baseColor[1] + s.random(-17, 17), 0, 255), 278 | s.constrain(baseColor[2] + s.random(-17, 17), 0, 255) 279 | ]); 280 | } 281 | 282 | bloom() { 283 | if (this.progress >= 1) { 284 | this.display(); 285 | return; 286 | } 287 | // Calc progress 288 | this.progress = this.t / this.bloomTime; 289 | // MODE 2 290 | s.push(); 291 | s.textSize(this.radiusScale * this.height * s.map(this.progress, 0, 1, 0.9, 1)); 292 | s.translate(this.x, this.y); 293 | for (let i = 0; i < s.floor(this.angle * this.progress); i++) { 294 | s.push(); 295 | s.fill( 296 | this.baseColorSet[i][0], 297 | this.baseColorSet[i][1], 298 | this.baseColorSet[i][2], 299 | 225 - 7 * i 300 | ); 301 | s.rotate(i * 360 / this.angle); 302 | s.translate(this.radiusScale * this.offset[0], this.radiusScale * this.offset[1]); 303 | s.text(this.char, 0, 0); 304 | s.pop(); 305 | } 306 | s.pop(); 307 | 308 | this.t++; 309 | } 310 | 311 | display() { 312 | s.push(); 313 | s.textSize(this.radiusScale * this.height); 314 | s.translate(this.x, this.y); 315 | for (let i = 0; i < this.angle; i++) { 316 | s.push(); 317 | s.fill( 318 | this.baseColorSet[i][0], 319 | this.baseColorSet[i][1], 320 | this.baseColorSet[i][2], 321 | 225 - 7 * i 322 | ); 323 | s.rotate(i * 360 / this.angle); 324 | s.translate(this.radiusScale * this.offset[0], this.radiusScale * this.offset[1]); 325 | s.text(this.char, 0, 0); 326 | s.pop(); 327 | } 328 | s.pop(); 329 | } 330 | } 331 | 332 | let validSelection = () => { 333 | // Check if it is a valid selection 334 | return ( 335 | (window.getSelection().anchorNode != null) && 336 | (window.getSelection().focusNode != null) && 337 | (window.getSelection().focusOffset != window.getSelection().anchorOffset) 338 | ); 339 | }; 340 | 341 | let saveIt = () => { 342 | s.saveCanvas(c, document.title + '_' + saveCount.toString(), "png"); 343 | saveCount++; 344 | }; 345 | }; 346 | 347 | let findChoices = (a) => { 348 | // Find two colors out of a set of colors 349 | let returner = []; 350 | for (let i = 0; i < a.length - 1; i++) { 351 | let temp = a.slice(i, i + 1); 352 | for (let j = i + 1; j < a.length; j++) { 353 | returner.push(temp.concat(a.slice(j, j + 1))); 354 | } 355 | } 356 | return returner; 357 | }; 358 | 359 | let windowFlowerSketch = new p5(sketch); 360 | 361 | // Receive language message from background 362 | chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { 363 | if (debug) console.log(bloomLogMsg[0]); 364 | pageLang = msg.data; 365 | windowFlowerSketch.setFont(); 366 | }); -------------------------------------------------------------------------------- /scripts/jquery-3.5.0.slim.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="",m.option=!!le.lastChild;var he={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p\s*$/g;function Le(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n
",2===ft.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Fe(m.pixelPosition,function(e,t){if(t)return t=We(e,n),Ie.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0