├── .gitignore ├── README.md ├── dist ├── bubb.min.css ├── bubb.min.css.gz ├── bubb.min.js └── bubb.min.js.gz ├── docs ├── CNAME ├── assets │ ├── bubb.min.css │ ├── bubb.min.js │ ├── demo.min.css │ ├── demo.min.js │ └── images │ │ ├── bubb.gif │ │ ├── bubb.png │ │ ├── bubb_720.gif │ │ ├── bubble_bobble.png │ │ ├── circle.png │ │ ├── circle.svg │ │ ├── cirque.svg │ │ ├── ghost.png │ │ └── icons │ │ ├── color │ │ ├── facebook.svg │ │ ├── reddit.svg │ │ └── twitter.svg │ │ ├── facebook.svg │ │ ├── fill │ │ ├── facebook.svg │ │ ├── reddit.svg │ │ └── twitter.svg │ │ ├── github.svg │ │ ├── reddit.svg │ │ └── twitter.svg ├── index.html └── libs │ ├── css │ └── tomorrow.min.css │ └── js │ └── highlight.min.js ├── gruntfile.js ├── html └── index.html ├── js ├── demo.js └── script.js ├── package-lock.json ├── package.json └── scss ├── bubb.scss └── demo.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | .DS_Store 4 | _dev_*.* 5 | fonts 6 | .log 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alt text](/docs/assets/images/bubb.gif?raw=true "Bubb") 2 | 3 | **Non-dependent**, non-fancy javascript _infotip_. **No CSS needed**. 4 | 5 | [![dependencies Status](https://david-dm.org/frdnrdb/bubb/status.svg)](https://david-dm.org/frdnrdb/bubb) 6 | [![dev dependencies](https://david-dm.org/frdnrdb/bubb/dev-status.svg)](https://badge.fury.io/js/bubb) 7 | [![npm version](https://img.shields.io/badge/trump-sad-red.svg)](http://bubb.surge.sh) 8 | 9 | --- 10 | 11 | * **2.0.1** Options: toggle, autoDirection, autoHide 12 | * **next** Mobile solution 13 | 14 | --- 15 | 16 | #### Usage 17 | [→ See some examples](http://bubb.surge.sh) 18 | 19 | ```html 20 | 21 | 22 | 23 | info 24 | menu 25 | 26 | 44 | ``` 45 | ```js 46 | 47 | // yarn add bubb | npm i --save bubb 48 | const bubb = require('bubb'); 49 | ``` 50 | 51 | #### Options setup 52 | 53 | ```js 54 | 55 | const config = { 56 | bubble: { 57 | text: 'content', 58 | _: { 59 | // ... bubble options 60 | } 61 | }, 62 | bobble: { 63 | menu_item_1: 'content', 64 | menu_item_2: 'content', 65 | _: { 66 | // ... bobble options 67 | } 68 | }, 69 | _: { 70 | // ... global options 71 | } 72 | } 73 | 74 | ``` 75 | 76 | #### options 77 | 78 | ```js 79 | 80 | callback: false 81 | // function(){} overrides initial (or global) callback 82 | // boolean true adds click listener and reports to default callback 83 | 84 | transitionOff: false 85 | // boolean 86 | 87 | interactive: false 88 | // boolean, default true for menus and option callback 89 | 90 | hoverCallback: false 91 | // boolean, trigger callback on element:hover 92 | 93 | delay: false 94 | // int value, microseconds reveal delay 95 | 96 | autoHide: false 97 | // false or milliseconds 98 | 99 | toggle: false 100 | // boolean, activate tooltip with function call bubb.toggle(key) 101 | 102 | direction: false 103 | // string 'north', 'west' or 'east' (default false = 'south') 104 | 105 | autoDirection: false 106 | // boolean, screen edge proximity aware direction change 107 | 108 | anchor: false 109 | // string 'left' or 'right' (default false = 'centered') 110 | 111 | width: false 112 | // int value <= 100 (document width percentage) 113 | // css string with units (eg. '300px') 114 | // querySelector string (eg. 'section:first-of-type') 115 | 116 | borderRadius: '4px' 117 | // css string with units 118 | 119 | fontSize: '17px' 120 | // css string with units 121 | 122 | background: '#444' 123 | // css color string 124 | 125 | color: '#fff' 126 | // css color string 127 | 128 | class: false 129 | // string, className to target current bubb specifically 130 | 131 | 132 | ``` 133 | 134 | #### Methods 135 | 136 | ```js 137 | 138 | bubb.refresh(); 139 | // initialize new data-bubb elements added to DOM 140 | 141 | bubb.update(reference, content | options); 142 | 143 | bubb.update(menu_reference, options); 144 | bubb.update(menu_reference.menu_item, content); 145 | 146 | bubb.add(menu_reference.menu_item, content); 147 | bubb.remove(menu_reference.menu_item); 148 | // these methods adds or removes DOM elements 149 | 150 | ``` 151 | 152 | 153 | #### Style overrides 154 | The content is targeted through **bubb-content** > **div**. 155 | The trigger element gets className **.bubb** *(and .bubb-menu)*. 156 | The bubb(le) tagname is **bubb-bobb**. 157 | 158 | --- 159 | 160 | #### Browser Support et cetera 161 | 162 | Missed that train. Feel free to contribute if you're on board. 163 | -------------------------------------------------------------------------------- /dist/bubb.min.css: -------------------------------------------------------------------------------- 1 | [bubb] { 2 | position: relative; } 3 | [bubb]:before, [bubb]:after { 4 | position: absolute; 5 | pointer-events: none; 6 | visibility: hidden; 7 | z-index: 1; 8 | bottom: 0; 9 | opacity: 0; } 10 | [bubb]:before { 11 | content: ''; 12 | width: 0; 13 | height: 0; 14 | border-bottom: 10px solid #444; 15 | border-right: 10px solid transparent; 16 | border-left: 10px solid transparent; } 17 | [bubb]:after { 18 | content: attr(bubb); 19 | padding: .75em .9em .85em; 20 | line-height: 1.1; 21 | font-size: 17px; 22 | text-align: center; 23 | border-radius: 5px; 24 | background-color: #444; 25 | color: #fff; 26 | min-width: 9em; 27 | text-rendering: optimizeLegibility; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-osx-font-smoothing: grayscale; 30 | -webkit-touch-callout: none; 31 | -webkit-user-select: none; 32 | -khtml-user-select: none; 33 | -moz-user-select: none; 34 | -ms-user-select: none; 35 | user-select: none; } 36 | [bubb]:hover:before, [bubb]:hover:after { 37 | transition: transform 0.35s cubic-bezier(0, 0, 0, 1), opacity 0.35s cubic-bezier(0, 0, 0, 1); 38 | visibility: visible; 39 | opacity: 1; } 40 | [bubb][still]:hover:before, [bubb][still]:hover:after { 41 | transition-duration: 0s; } 42 | [bubb][delay]:hover:before, [bubb][delay]:hover:after { 43 | transition-delay: 0.75s; } 44 | [bubb][round]:after { 45 | border-radius: 30px !important; } 46 | [bubb][round][north][left]:after, [bubb][round][east][right]:after { 47 | border-radius: 30px 30px 30px 0 !important; } 48 | [bubb][round][north][right]:after, [bubb][round][west][right]:after { 49 | border-radius: 30px 30px 0 30px !important; } 50 | [bubb][round][left]:not([east]):not([west]):not([north]):after, [bubb][round][east][left]:after { 51 | border-radius: 0 30px 30px 30px !important; } 52 | [bubb][round][right]:not([east]):not([west]):not([north]):after, [bubb][round][west][left]:after { 53 | border-radius: 30px 0 30px 30px !important; } 54 | [bubb][large]:after { 55 | font-size: 20px !important; } 56 | [bubb]:not([east]):not([west]):not([north]):before, [bubb]:not([east]):not([west]):not([north]):after { 57 | left: 50%; } 58 | [bubb]:not([east]):not([west]):not([north]):before { 59 | transform: translate(-50%, calc( 10px + .065rem + 4px + 20px)); } 60 | [bubb]:not([east]):not([west]):not([north]):after { 61 | transform: translate(-50%, calc( 100% + 10px + 4px + 20px)); } 62 | [bubb]:not([east]):not([west]):not([north]):hover:before { 63 | transform: translate(-50%, calc( 10px + .065rem + 4px)); } 64 | [bubb]:not([east]):not([west]):not([north]):hover:after { 65 | transform: translate(-50%, calc( 100% + 10px + 4px)); } 66 | [bubb]:not([east]):not([west]):not([north])[left]:before, [bubb]:not([east]):not([west]):not([north])[right]:before { 67 | transform: translate(0, calc( 10px + .065rem + 4px + 20px)); } 68 | [bubb]:not([east]):not([west]):not([north])[left]:after, [bubb]:not([east]):not([west]):not([north])[right]:after { 69 | transform: translate(0, calc( 100% + 10px + 4px + 20px)); } 70 | [bubb]:not([east]):not([west]):not([north])[left]:hover:before, [bubb]:not([east]):not([west]):not([north])[right]:hover:before { 71 | transform: translate(0, calc( 10px + .065rem + 4px)); } 72 | [bubb]:not([east]):not([west]):not([north])[left]:hover:after, [bubb]:not([east]):not([west]):not([north])[right]:hover:after { 73 | transform: translate(0, calc( 100% + 10px + 4px)); } 74 | [bubb]:not([east]):not([west]):not([north])[left]:before, [bubb]:not([east]):not([west]):not([north])[left]:after { 75 | left: 0; } 76 | [bubb]:not([east]):not([west]):not([north])[left]:before { 77 | border-left: none; } 78 | [bubb]:not([east]):not([west]):not([north])[left]:after { 79 | border-radius: 0 5px 5px 5px; } 80 | [bubb]:not([east]):not([west]):not([north])[right]:before, [bubb]:not([east]):not([west]):not([north])[right]:after { 81 | left: auto; 82 | right: 0; } 83 | [bubb]:not([east]):not([west]):not([north])[right]:before { 84 | border-right: none; } 85 | [bubb]:not([east]):not([west]):not([north])[right]:after { 86 | border-radius: 5px 0 5px 5px; } 87 | [bubb][north]:before, [bubb][north]:after { 88 | left: 50%; 89 | top: 0; 90 | bottom: auto; } 91 | [bubb][north]:before { 92 | border-bottom: none; 93 | border-top: 10px solid #444; 94 | border-left-color: transparent; 95 | border-right-color: transparent; 96 | transform: translate(-50%, calc( -10px - .065rem - 4px - 20px)); } 97 | [bubb][north]:after { 98 | transform: translate(-50%, calc( -100% - 10px - 4px - 20px)); } 99 | [bubb][north]:hover:before { 100 | transform: translate(-50%, calc( -10px - .065rem - 4px)); } 101 | [bubb][north]:hover:after { 102 | transform: translate(-50%, calc( -100% - 10px - 4px)); } 103 | [bubb][north][left]:before, [bubb][north][right]:before { 104 | transform: translate(0, calc( -10px - .065rem - 4px - 20px)); } 105 | [bubb][north][left]:after, [bubb][north][right]:after { 106 | transform: translate(0, calc( -100% - 10px - 4px - 20px)); } 107 | [bubb][north][left]:hover:before, [bubb][north][right]:hover:before { 108 | transform: translate(0, calc( -10px - .065rem - 4px)); } 109 | [bubb][north][left]:hover:after, [bubb][north][right]:hover:after { 110 | transform: translate(0, calc( -100% - 10px - 4px)); } 111 | [bubb][north][left]:before, [bubb][north][left]:after { 112 | left: 0; 113 | top: 0; 114 | bottom: auto; } 115 | [bubb][north][left]:before { 116 | border-left: none; } 117 | [bubb][north][left]:after { 118 | border-radius: 5px 5px 5px 0; } 119 | [bubb][north][right]:before, [bubb][north][right]:after { 120 | left: auto; 121 | right: 0; 122 | top: 0; 123 | bottom: auto; } 124 | [bubb][north][right]:before { 125 | border-right: none; } 126 | [bubb][north][right]:after { 127 | border-radius: 5px 5px 0 5px; } 128 | [bubb][east]:before, [bubb][east]:after { 129 | left: auto; 130 | right: 0; 131 | top: 50%; 132 | bottom: auto; } 133 | [bubb][east]:before { 134 | border-left: none; 135 | border-right-color: #444; } 136 | [bubb][east]:before { 137 | border-top: 10px solid transparent; 138 | border-bottom-color: transparent; 139 | transform: translate(calc( 10px + .065rem + 4px + 20px), -50%); } 140 | [bubb][east]:after { 141 | transform: translate(calc( 100% + 10px + 4px + 20px), -50%); } 142 | [bubb][east]:hover:before { 143 | transform: translate(calc( 10px + .065rem + 4px), -50%); } 144 | [bubb][east]:hover:after { 145 | transform: translate(calc( 100% + 10px + 4px), -50%); } 146 | [bubb][east][left]:before { 147 | border-top: none; 148 | border-bottom-color: transparent; 149 | transform: translate(calc( 10px + .065rem + 4px + 20px), 0); } 150 | [bubb][east][left]:after { 151 | border-radius: 0 5px 5px 5px; 152 | transform: translate(calc( 100% + 10px + 4px + 20px), 0); } 153 | [bubb][east][left]:hover:before { 154 | transform: translate(calc( 10px + .065rem + 4px), 0); } 155 | [bubb][east][left]:hover:after { 156 | transform: translate(calc( 100% + 10px + 4px), 0); } 157 | [bubb][east][right]:before { 158 | border-top: 10px solid transparent; 159 | border-bottom: none; 160 | transform: translate(calc( 10px + .065rem + 4px + 20px), -100%); } 161 | [bubb][east][right]:after { 162 | border-radius: 5px 5px 5px 0; 163 | transform: translate(calc( 100% + 10px + 4px + 20px), -100%); } 164 | [bubb][east][right]:hover:before { 165 | transform: translate(calc( 10px + .065rem + 4px), -100%); } 166 | [bubb][east][right]:hover:after { 167 | transform: translate(calc( 100% + 10px + 4px), -100%); } 168 | [bubb][west]:before, [bubb][west]:after { 169 | left: 0; 170 | right: auto; 171 | top: 50%; 172 | bottom: auto; } 173 | [bubb][west]:before { 174 | border-right: none; 175 | border-top: 10px solid transparent; 176 | border-bottom-color: transparent; 177 | border-left-color: #444; } 178 | [bubb][west]:before { 179 | transform: translate(calc( -10px - .065rem - 4px - 20px), -50%); } 180 | [bubb][west]:after { 181 | transform: translate(calc( -100% - 10px - 4px - 20px), -50%); } 182 | [bubb][west]:hover:before { 183 | transform: translate(calc( -10px - .065rem - 4px), -50%); } 184 | [bubb][west]:hover:after { 185 | transform: translate(calc( -100% - 10px - 4px), -50%); } 186 | [bubb][west][left]:before { 187 | border-top: none; 188 | transform: translate(calc( -10px - .065rem - 4px - 20px), 0); } 189 | [bubb][west][left]:after { 190 | border-radius: 5px 0 5px 5px; 191 | transform: translate(calc( -100% - 10px - 4px - 20px), 0); } 192 | [bubb][west][left]:hover:before { 193 | transform: translate(calc( -10px - .065rem - 4px), 0); } 194 | [bubb][west][left]:hover:after { 195 | transform: translate(calc( -100% - 10px - 4px), 0); } 196 | [bubb][west][right]:before { 197 | border-bottom: none; 198 | transform: translate(calc( -10px - .065rem - 4px - 20px), -100%); } 199 | [bubb][west][right]:after { 200 | border-radius: 5px 5px 0 5px; 201 | transform: translate(calc( -100% - 10px - 4px - 20px), -100%); } 202 | [bubb][west][right]:hover:before { 203 | transform: translate(calc( -10px - .065rem - 4px), -100%); } 204 | [bubb][west][right]:hover:after { 205 | transform: translate(calc( -100% - 10px - 4px), -100%); } 206 | -------------------------------------------------------------------------------- /dist/bubb.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/dist/bubb.min.css.gz -------------------------------------------------------------------------------- /dist/bubb.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | TODO! 4 | 5 | * IMPROVE add eventEmitter 6 | - https://www.sitepoint.com/nodejs-events-and-eventemitter/ 7 | - https://github.com/chrisdavies/eev 8 | 9 | * IMPROVE Implement some try/catch error handling and/or input type checks to prevent user input errors 10 | 11 | * ADD Implement dim-rest-of-page-option 12 | 13 | * TRY alternative tooltip layout (material-ui-dropdown-menu-ish) as option 14 | * TRY alternative theme (light (box-shadowed) or thin border) as option 15 | 16 | * CONSIDER Menu-style as part of package 17 | 18 | */ 19 | 20 | (function(window, document) {var this$0 = this; 21 | 22 | var bubb = function(config, callback) { 23 | 24 | bubb.config = bubb.config || (typeof config === 'object' ? config : {}); 25 | bubb.config._ = bubb.config._ || config._ || {}; 26 | bubb.callback = bubb.callback || (typeof bubb.config._.callback === 'function' && bubb.config._.callback) || (typeof callback === 'function' && callback); 27 | 28 | // class bubb indicates the element has already been initiated/ configured 29 | var bubbs = arguments[0] === 'update' ? [arguments[1]] : Array.from( document.querySelectorAll('[data-bubb]:not(.bubb)') ); 30 | 31 | if (!bubbs.length) return; 32 | 33 | !bubb._initialized && initBubb(); 34 | 35 | bubbs.forEach(buildElement); 36 | 37 | } 38 | 39 | var buildElement = function(_trigger ) { 40 | 41 | var key = _trigger.dataset.bubb.trim(), 42 | data = bubb.config[key] || key, 43 | 44 | chck = typeof data === 'object', 45 | opts = chck && data._, // config contains options _ 46 | only = opts && Object.keys(data).length === 1, // options only, use key as content 47 | menu = chck && !data.hasOwnProperty('text'); // config indicates a menu 48 | 49 | only && (data.text = key); 50 | 51 | var props = !menu ? opts ? ['text'] : [false] : Object.keys(data), 52 | bindMenu = typeof bubb.callback === 'function' || typeof bubb.config._.callback === 'function', 53 | triggerPosition = window.getComputedStyle(_trigger).position; 54 | 55 | _trigger._bubb = { 56 | key: key, 57 | config: setElementConfiguration(opts), 58 | type: menu ? 'menu' : opts ? 'opts' : 'string', 59 | markup: props.reduce( buildElementMarkup.bind(this$0, key), '' ), 60 | bind: (menu && bindMenu) || (opts && (typeof opts.callback === 'function' || typeof opts.callback === 'boolean')) 61 | }; 62 | 63 | triggerPosition && !triggerPosition.match(/absolute|fixed|relative/) && (_trigger.style.position = 'relative'); 64 | 65 | _trigger.classList.add('bubb'); 66 | 67 | bubb.triggers[key] = _trigger; 68 | 69 | bindElement(_trigger); 70 | 71 | }; 72 | 73 | var setElementConfiguration = function(opts ) { 74 | 75 | return availableOptions.reduce( function(config, option) { 76 | opts && opts.hasOwnProperty(option) ? config[option] = opts[option] 77 | : bubb.config._.hasOwnProperty(option) ? config[option] = bubb.config._[option] 78 | : false; 79 | return config; 80 | }, {}); 81 | 82 | }; 83 | 84 | var buildElementMarkup = function(key, markup, prop) { 85 | 86 | if (prop === '_') return markup; 87 | 88 | var content = prop ? bubb.config[key][prop] : bubb.config[key] || key, 89 | selector = key + (prop && prop !== 'text' ? '.' + prop : ''), 90 | attribute = ((" data-bubb-value=\"" + selector) + "\""); 91 | 92 | return markup += (("
" + content) + "
"); 93 | 94 | }; 95 | 96 | var bindElement = function(_trigger) { 97 | 98 | var bubbEvents = _trigger._bubb.config.toggle ? [] : ['mouseenter', 'mouseleave']; 99 | 100 | if (_trigger._bubb.bind) bubbEvents.push('mousedown'); 101 | 102 | bubbEvents.forEach( function(event ) {return _trigger.addEventListener(event, eventHandler, false)} ); 103 | 104 | }; 105 | 106 | var configureElement = function(_trigger ) { 107 | 108 | var trigger = _trigger._bubb, 109 | bubble = bubb._element, 110 | config = bubble._config = trigger.config; 111 | 112 | bubble.style.visibility = 'hidden'; 113 | 114 | bubble._elementContent.innerHTML = trigger.markup; 115 | 116 | bubble._bind = (trigger.bind && (config.interactive !== false)) || config.toggle; 117 | 118 | bubble.className = config.class || ''; 119 | 120 | setWidth(config, _trigger, bubble); 121 | 122 | trigger.type === 'menu' && _trigger.classList.add('bubb-menu'); 123 | 124 | _trigger.appendChild(bubble); 125 | bubb._trigger = _trigger; 126 | bubb._visible = false; 127 | 128 | appendStyles(bubb._element, '_bubblePreactive'); 129 | 130 | }; 131 | 132 | var bubbShow = function() { 133 | 134 | appendStyles(bubb._element, '_bubbleActive'); 135 | bubb._visible = true; 136 | 137 | }; 138 | 139 | var bubbHide = function(e) { 140 | 141 | appendStyles(bubb._element, ['_bubbleInactive', '_bubblePreactive']); 142 | bubb._visible = false; 143 | 144 | }; 145 | 146 | var autoDirection = function(e, target) { 147 | 148 | if (!e) return; 149 | 150 | var h = bubb._element.offsetHeight || 150; 151 | var w = bubb._element.offsetWidth || 150; 152 | var d = target._bubb.config.direction; 153 | 154 | var rect = target.getBoundingClientRect(); 155 | 156 | var limits = { 157 | w: rect.left < w, 158 | e: rect.right > (window.innerWidth - w), 159 | n: rect.top < h, 160 | s: rect.bottom > (window.innerHeight - h) 161 | } 162 | 163 | if (d && (d === 'east' || d === 'west')) { 164 | bubb._autoDirection = limits.w ? 'east' : limits.e ? 'west' : false; 165 | bubb._autoAnchor = limits.n ? 'left' : limits.s ? 'right' : false; 166 | } 167 | else { 168 | bubb._autoDirection = limits.s ? 'north' : limits.n ? 'south' : false; 169 | bubb._autoAnchor = false; 170 | } 171 | 172 | return bubb._autoDirection; 173 | 174 | }; 175 | 176 | var eventHandler = function(e) { 177 | 178 | if (!this._bubb) return; 179 | 180 | // ---> configure (if autoDirection in config OR trigger new element) 181 | 182 | ( 183 | ((bubb.config._.autoDirection || this._bubb.config.autoDirection) 184 | && autoDirection(e, this) 185 | ) 186 | || (bubb._trigger !== this) 187 | ) 188 | && configureElement(this); 189 | 190 | // ---> reveal or hide 191 | 192 | window.clearTimeout(bubb._timerOn); 193 | window.clearTimeout(bubb._timerOff); 194 | 195 | e.type === 'mouseenter' 196 | ? bubb._timerOn = window.setTimeout( bubbShow, (this._bubb.config.delay | 0) || 0) 197 | : e.type !== 'mousedown' && bubbHide(e); 198 | 199 | if (this._bubb.config.autoHide) { 200 | bubb._timerOff = window.setTimeout( bubbHide, this._bubb.config.autoHide + (this._bubb.config.delay | 0) ); 201 | } 202 | 203 | // ---> leave 204 | 205 | if (!this._bubb.bind || e.type === 'mouseleave') return; 206 | 207 | // ---> callback 208 | 209 | var hover = this._bubb.config.hoverCallback, 210 | bubbvalue = hover ? this.dataset.bubb : e.target.dataset.bubbValue || e.target.parentNode.dataset.bubbValue || e.target.parentNode.parentNode.dataset.bubbValue; 211 | 212 | if (!bubbvalue) return; 213 | 214 | var thiscallback = (typeof this._bubb.config.callback === 'function' && this._bubb.config.callback) || bubb.callback, 215 | item = this.querySelector((("[data-bubb-value=\"" + (this.dataset.bubb)) + "\"]")) || e.target; 216 | 217 | thiscallback(bubbvalue, item, this, e.type); 218 | 219 | } 220 | 221 | var isTrigger = function(node, check) { 222 | 223 | if (!node) return; 224 | if (node._bubb && (check ? node === bubb._trigger : true)) return node; 225 | return isTrigger(node.parentNode); 226 | 227 | }; 228 | 229 | var clickOutside = function(e) { 230 | 231 | // _toggler initiated the toggle 232 | 233 | !bubb._toggler && (bubb._toggler = e.target); 234 | 235 | // avoid toggle initiator to negate the toggle 236 | 237 | if (bubb._toggler === e.target) return; 238 | 239 | // click inside; the caller is the toggled element 240 | 241 | if (isTrigger(e.target, bubb._trigger)) return; 242 | 243 | // release _toggler, remove event listener and hide bubb 244 | 245 | bubb._toggler = false; 246 | window.removeEventListener('click', clickOutside, false); 247 | eventHandler.call(bubb._trigger, {target: bubb._trigger, type: 'mouseleave'}); 248 | 249 | } 250 | 251 | var toggle = function() { 252 | 253 | var element = arguments[0], 254 | _trigger = (typeof element === 'object' && element) || bubb.triggers[element]; 255 | 256 | if (!_trigger || !_trigger._bubb.config.toggle) { 257 | console.error('bubb: trying to toggle a non-existing or non-toggled element'); 258 | return; 259 | } 260 | 261 | //_trigger !== bubb._trigger && configureElement(_trigger); 262 | 263 | window[ (bubb._visible ? 'remove' : 'add') + 'EventListener']('click', clickOutside, false); 264 | eventHandler.call(_trigger, {target: _trigger, type: bubb._visible ? 'mouseleave' : 'mouseenter'}); 265 | 266 | }; 267 | 268 | var update = function() { 269 | 270 | var key = arguments[0], contentOrConfig = arguments[1]; 271 | 272 | if (typeof key !== 'string' || !contentOrConfig) return; 273 | 274 | var updateOptions = typeof contentOrConfig === 'object', 275 | menu = key.split('.').reduce( function(obj, val, i) { 276 | obj[['key','val'][i]] = val; 277 | return obj; 278 | }, {}), 279 | _trigger = document.querySelector((("[data-bubb=\"" + (menu.key || key)) + "\"]")); 280 | 281 | if (!_trigger && !updateOptions) return; 282 | 283 | if (!_trigger) { 284 | console.error('bubb: trying to update a non-existing element'); 285 | return; 286 | } 287 | 288 | // update added DOM element - when bubb.refresh() has not been used 289 | 290 | if (!_trigger._bubb) bubb('update', _trigger); 291 | 292 | bubb._trigger = false; 293 | 294 | // update element 295 | 296 | if (!updateOptions) { 297 | _trigger._bubb.markup = _trigger._bubb.markup.replace(new RegExp(((".*?")), (("
" + contentOrConfig) + "
")); 298 | return; 299 | } 300 | 301 | // update element config 302 | 303 | var bindDefault = typeof contentOrConfig.callback === 'boolean' && !_trigger._bubb.bind, 304 | bindSelf = typeof contentOrConfig.callback === 'function' && !_trigger._bubb.config.hasOwnProperty('callback'), 305 | bindHover = contentOrConfig.hoverCallback && !_trigger._bubb.config.hoverCallback; 306 | 307 | if (bindDefault || bindSelf || bindHover) _trigger._bubb.bind = true; 308 | 309 | if (bindDefault || bindSelf) _trigger.addEventListener( 'mousedown', eventHandler, false); 310 | 311 | Object.keys(contentOrConfig).forEach( function(updatedKey ) { 312 | if (!~availableOptions.indexOf(updatedKey)) return; 313 | if (_trigger) _trigger._bubb.config[updatedKey] = contentOrConfig[updatedKey]; 314 | else bubb.config['_'][updatedKey] = contentOrConfig[updatedKey]; 315 | }); 316 | 317 | // update main config 318 | 319 | updateMainConfig(key, contentOrConfig, updateOptions, _trigger); 320 | 321 | }; 322 | 323 | var addOrRemove = function() { 324 | 325 | if (arguments.length === 0) { 326 | bubb(); 327 | return; 328 | } 329 | 330 | var key = arguments[0], value = arguments[1], 331 | menu = key.split('.').reduce( function(obj, val, i) { 332 | obj[['key','val'][i]] = val; 333 | return obj; 334 | }, {}); 335 | 336 | if (!menu.val || !bubb.config[menu.key]) return; 337 | 338 | // add menu item 339 | 340 | bubb._trigger = false; 341 | 342 | if (value && typeof value === 'string' && !bubb.config[menu.key][menu.val]) { 343 | 344 | bubb.config[menu.key][menu.val] = value; 345 | 346 | document.querySelector((("[data-bubb=\"" + (menu.key)) + "\"]"))._bubb.markup += (("
" + value) + "
"); 347 | return; 348 | 349 | } 350 | 351 | // remove menu item 352 | 353 | if (!value && bubb.config[menu.key][menu.val]) { 354 | 355 | delete bubb.config[menu.key][menu.val]; 356 | 357 | var _trigger = document.querySelector((("[data-bubb=\"" + (menu.key)) + "\"]")); 358 | _trigger._bubb.markup = _trigger._bubb.markup.replace(new RegExp(((".*?")), ''); 359 | 360 | } 361 | 362 | }; 363 | 364 | var setWidth = function(config, _trigger, _bubble) { 365 | 366 | var input = config.width, 367 | anchor = config.anchor, 368 | direction = config.direction, 369 | sideways = direction === 'east' || direction === 'west'; 370 | 371 | // '300px', '3em' 372 | if (typeof input === 'string' && parseInt(input)) { 373 | _bubble.style.width = input; 374 | return; 375 | } 376 | 377 | // 33, '33' || 'section > div' 378 | var width = (input | 0) || document.querySelector(input); 379 | 380 | if (!width) { 381 | _bubble.style.width = '100%'; 382 | return; 383 | } 384 | 385 | var padding = 30, 386 | fill = typeof width === 'object', 387 | bodyw = document.body.offsetWidth, 388 | box = _trigger.getBoundingClientRect(), 389 | boxm = box.width/2, 390 | boxl = box.left, 391 | boxr = box.right, 392 | inputWidth = fill ? width.offsetWidth : (width === 100 ? bodyw - padding*2 : (width * bodyw) / 100); 393 | 394 | if (anchor || sideways) { 395 | 396 | var newWidth$0 = !sideways && anchor 397 | ? anchor === 'left' 398 | ? bodyw - boxl - padding : anchor === 'right' 399 | ? bodyw - ( bodyw - boxr ) - padding 400 | : false 401 | : direction === 'east' 402 | ? bodyw - boxr - padding : direction === 'west' 403 | ? boxl - padding 404 | : false; 405 | 406 | if (newWidth$0) { 407 | _bubble.style.width = Math.min(inputWidth, newWidth$0) + 'px'; 408 | return; 409 | } 410 | 411 | } 412 | 413 | var newWidth = Math.min(inputWidth, ((boxl + boxm > bodyw/2 ? bodyw - boxr + boxm: boxl + boxm) - padding) * 2); 414 | 415 | _bubble.style.width = newWidth + 'px'; 416 | _bubble.style.left = (boxm - newWidth/2) + 'px'; 417 | _bubble._elementTip.style.left = newWidth/2 + 'px'; 418 | 419 | }; 420 | 421 | var updateMainConfig = function(key, contentOrConfig, updateOptions, _trigger) { 422 | 423 | var typeMenu = _trigger._bubb.type === 'menu', 424 | typeOptions = _trigger._bubb.type === 'opts', 425 | keyVal = ~key.indexOf('.') && key.split('.'), 426 | menu = keyVal ? keyVal.reduce( function(obj, val, i) { 427 | obj[['key','val'][i]] = val; 428 | return obj; 429 | }, {}) : {}; 430 | 431 | if (!typeMenu && !typeOptions && !updateOptions) { 432 | bubb.config[key] = contentOrConfig; 433 | return; 434 | } 435 | 436 | var prop = menu.key || key; 437 | 438 | if (!updateOptions) { 439 | bubb.config[prop][menu.val || 'text'] = contentOrConfig; 440 | return; 441 | } 442 | 443 | if (!typeMenu && !typeOptions) { 444 | bubb.config[prop] = { 445 | text: bubb.config[prop], 446 | _: contentOrConfig 447 | }; 448 | _trigger._bubb.type === 'opts'; 449 | return; 450 | } 451 | 452 | bubb.config[prop]['_'] = bubb.config[prop]['_'] || contentOrConfig; 453 | Object.assign(bubb.config[prop]['_'], contentOrConfig); 454 | 455 | }; 456 | 457 | var styleVariables = { 458 | tipsize: '12px', 459 | offset: '.15em', 460 | distance: '20px', 461 | easing: 'cubic-bezier(0,0,0,1)', 462 | duration: '.3s', 463 | background: '#444', 464 | color: '#fff', 465 | rounding: '4px', 466 | fontsize: '17px' 467 | }; 468 | 469 | var styles = { 470 | _bubble: { 471 | position: 'absolute', 472 | zIndex: '99', 473 | display: 'block', 474 | padding: '.75em .9em .85em', 475 | lineHeight: '1.1', 476 | textAlign: 'center', 477 | cursor: 'default', 478 | minWidth: '150px', 479 | width: '100%', 480 | boxSizing: 'border-box', 481 | textRendering: 'optimizeLegibility', 482 | WebkitFontSmoothing: 'antialiased', 483 | MozOsxFontSmoothing: 'grayscale', 484 | wordWrap: 'break-word', 485 | hyphens: 'auto', 486 | WebkitTouchCallout: 'none', 487 | WebkitUserSelect: 'none', 488 | KhtmlUserSelect: 'none', 489 | MozUserSelect: 'none', 490 | MsUserSelect: 'none', 491 | userSelect: 'none' 492 | }, 493 | _bubbleInactive: { 494 | visibility: 'hidden', 495 | pointerEvents: 'none', 496 | opacity: '0', 497 | }, 498 | _bubblePreactive: { 499 | background: styleVariables['background'], 500 | borderBottomColor: styleVariables['background'], 501 | color: styleVariables['color'], 502 | fontSize: styleVariables['fontsize'], 503 | }, 504 | _bubbleActive: { 505 | transitionProperty: 'opacity, transform', 506 | transitionDuration: styleVariables['duration'], 507 | transitionTimingFunction: styleVariables['easing'], 508 | pointerEvents: 'all', 509 | opacity: '1', 510 | visibility: 'visible' 511 | }, 512 | _bubbleInteractive: { 513 | position: 'absolute', 514 | zIndex: '-1', 515 | display: 'none', 516 | width: (("calc( 100% + ( " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + " ) * 2 )"), 517 | height: (("calc( 100% + ( " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + " ) * 2 )"), 518 | top: (("calc( -1 * ( " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + " ) )"), 519 | left: (("calc( -1 * ( " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + " ) )"), 520 | pointerEvents: 'all', 521 | background: 'transparent' 522 | }, 523 | _bubbleTip: { 524 | position: 'absolute', 525 | width: '0', 526 | height: '0', 527 | borderLeft: (("" + (styleVariables['tipsize'])) + " solid transparent"), 528 | borderRight: (("" + (styleVariables['tipsize'])) + " solid transparent"), 529 | borderBottomWidth: styleVariables['tipsize'], 530 | borderBottomStyle: 'solid', 531 | borderBottomColor: 'inherit' 532 | } 533 | }; 534 | 535 | var styleTransforms = { 536 | positive: { 537 | active: (("calc( 100% + " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + " )"), 538 | inactive: (("calc( 100% + " + (styleVariables['tipsize'])) + (" + " + (styleVariables['offset'])) + (" + " + (styleVariables['distance'])) + " )") 539 | }, 540 | negative: { 541 | active: (("calc( -100% - " + (styleVariables['tipsize'])) + (" - " + (styleVariables['offset'])) + " )"), 542 | inactive: (("calc( -100% - " + (styleVariables['tipsize'])) + (" - " + (styleVariables['offset'])) + (" - " + (styleVariables['distance'])) + " )") 543 | } 544 | }; 545 | 546 | var styleDirections = { 547 | x: { 548 | east: styleTransforms.positive, 549 | west: styleTransforms.negative 550 | }, 551 | y: { 552 | south: styleTransforms.positive, 553 | north: styleTransforms.negative, 554 | } 555 | }; 556 | 557 | var styleRoundings = { 558 | south: { 559 | left: [0,1,1,1], 560 | right: [1,0,1,1] 561 | }, 562 | north: { 563 | left: [1,1,1,0], 564 | right: [1,1,0,1] 565 | }, 566 | east: { 567 | left: [0,1,1,1], 568 | right: [1,1,1,0] 569 | }, 570 | west: { 571 | left: [1,0,1,1], 572 | right: [1,1,0,1] 573 | } 574 | }; 575 | 576 | var evalAnchor = function(left, anchor) {return anchor ? ( (anchor === 'left' && left) || (anchor === 'right' && !left) ? 0 : 'auto' ) : left ? '50%' : 'auto'}; 577 | 578 | var stylePositions = { 579 | south: { 580 | left: evalAnchor.bind(this, true), 581 | right: evalAnchor.bind(this, false), 582 | top: function(anchor, tip) {return tip ? (("calc( 2px - " + (styleVariables['tipsize'])) + " )") : 'auto'}, 583 | bottom: function(anchor, tip) {return tip ? 'auto' : 0} 584 | }, 585 | north: { 586 | left: function(anchor, tip) {return evalAnchor(true, tip && anchor ? anchor === 'left' ? 'left' : 'right' : anchor)}, 587 | right: function(anchor, tip) {return evalAnchor(false, tip && anchor ? anchor === 'left' ? 'left' : 'right' : anchor)}, 588 | top: function(anchor, tip) {return tip ? 'auto' : 0}, 589 | bottom: function(anchor, tip) {return tip ? (("calc( 2px - " + (styleVariables['tipsize'])) + " )") : 'auto'} 590 | }, 591 | east: { 592 | left: function(anchor, tip) {return tip ? (("calc( 2px - " + (styleVariables['tipsize'])) + " )") : 'auto'}, 593 | right: function(anchor, tip) {return tip ? 'auto' : 0}, 594 | top: function(anchor, tip) {return tip ? evalAnchor(true, anchor) : !anchor || (anchor === 'left') ? '50%' : 'auto'}, 595 | bottom: function(anchor, tip) {return tip ? evalAnchor(false, anchor) : anchor === 'right' ? '50%' : 'auto'} 596 | }, 597 | west: { 598 | left: function(anchor, tip) {return tip ? 'auto' : 0}, 599 | right: function(anchor, tip) {return tip ? (("calc( 2px - " + (styleVariables['tipsize'])) + " )") : 'auto'}, 600 | top: function(anchor, tip) {return tip ? evalAnchor(true, anchor) : !anchor || (anchor === 'left') ? '50%' : 'auto'}, 601 | bottom: function(anchor, tip) {return tip ? evalAnchor(false, anchor) : anchor === 'right' ? '50%' : 'auto'} 602 | } 603 | }; 604 | 605 | var tipTransforms = { 606 | south: { 607 | center: 'translate(-50%, 0)', 608 | left: 'translate(-25%, 50%) rotate(90deg)', 609 | right: 'translate(25%, 50%) rotate(-90deg)' 610 | }, 611 | north: { 612 | center: 'translate(-50%, 0) rotate(180deg)', 613 | left: 'translate(-25%, -50%) rotate(90deg)', 614 | right: 'translate(25%, -50%) rotate(-90deg)' 615 | }, 616 | east: { 617 | center: 'translate(-25%, -50%) rotate(-90deg)', 618 | left: 'translate(0, 0) rotate(180deg)', 619 | right: 'translate(0, 0)' 620 | }, 621 | west: { 622 | center: 'translate(25%, -50%) rotate(90deg)', 623 | left: 'translate(0, 0) rotate(180deg)', 624 | right: 'translate(0, 0)' 625 | } 626 | }; 627 | 628 | var setDirectionSpecificStyles = function(element, config, activeOrInactive) { 629 | 630 | var direction = bubb._autoDirection || config.direction || 'south', 631 | anchor = bubb._autoAnchor || config.anchor; 632 | 633 | var setBubbleTransform = function(xy ) {return ( typeof styleDirections[xy][direction] === 'object' && styleDirections[xy][direction][activeOrInactive] ) || ( anchor ? '0' : '-50%' )}; 634 | 635 | element.style.transform = (("translate(" + (setBubbleTransform('x'))) + (", " + (setBubbleTransform('y'))) + ")"); 636 | 637 | if (activeOrInactive === 'active') return; 638 | 639 | element._elementInteractive.style.display = element._bind ? 'block' : 'none'; 640 | 641 | element._elementTip.style.transform = tipTransforms[direction][anchor || 'center']; 642 | 643 | ['left', 'right', 'top', 'bottom'].forEach( function(position ) { 644 | element.style[position] = stylePositions[direction][position](anchor); 645 | element._elementTip.style[position] = stylePositions[direction][position](anchor, true); 646 | }); 647 | 648 | element.style.borderRadius = (styleRoundings[direction][anchor] || [1]).reduce( function(str, chk) { return str += (chk ? config.borderRadius || styleVariables['rounding'] : 0) + ' '; }, ''); 649 | 650 | }; 651 | 652 | var appendStyles = function(element, keys, init) { 653 | 654 | var config = element._config || {}; 655 | 656 | keys = typeof keys === 'string' ? [keys] : keys; 657 | 658 | keys.forEach( function(key ) { 659 | 660 | var active = key === '_bubbleActive', 661 | preactive = key === '_bubblePreactive', 662 | 663 | still = active && config.transitionOff, 664 | background = preactive && config.background, 665 | color = preactive && config.color, 666 | fontsize = preactive && config.fontSize; 667 | 668 | Object.keys(styles[key]).forEach( function(style ) { 669 | element.style[style] = init ? styles[key][style] 670 | : style === 'transitionDuration' && still ? '0s' 671 | : (style === 'background' || style === 'borderBottomColor') && background ? background 672 | : style === 'color' && color ? color 673 | : style === 'fontSize' && fontsize ? fontsize 674 | : styles[key][style]; 675 | }); 676 | 677 | if (!init && (active || preactive)) setDirectionSpecificStyles(element, config, active ? 'active' : 'inactive'); 678 | 679 | }); 680 | 681 | }; 682 | 683 | var setMethodProxies = function() { 684 | 685 | bubb.update = function() {return update.apply(this$0, arguments)}; 686 | bubb.add = bubb.refresh = bubb.remove = function() {return addOrRemove.apply(this$0, arguments)}; 687 | bubb.toggle = function() {return toggle.apply(this$0, arguments)}; 688 | 689 | }; 690 | 691 | var createBubbElements = function() { 692 | 693 | bubb._element = document.createElement('bubb-bobb'); 694 | 695 | var element = bubb._element, 696 | tagMap = { 697 | _elementInteractive: 'bubb-interactive', 698 | _elementTip: 'bubb-tip', 699 | _elementContent: 'bubb-content' 700 | }; 701 | 702 | for (var tag in tagMap) { 703 | element[tag] = document.createElement(tagMap[tag]); 704 | element.appendChild(element[tag]); 705 | } 706 | 707 | appendStyles(element, ['_bubble', '_bubbleInactive'], true); 708 | appendStyles(element._elementTip, '_bubbleTip', true); 709 | appendStyles(element._elementInteractive, '_bubbleInteractive', true); 710 | 711 | bubb._den = document.createElement('bubb-den'); 712 | bubb._den.style.display = 'none'; 713 | bubb._den.appendChild(element); 714 | 715 | document.body.appendChild(bubb._den); 716 | 717 | }; 718 | 719 | var listenToBubbEvents = function() { 720 | 721 | var hideOrKeep = function() {return bubb._element._bind || appendStyles(bubb._element, '_bubbleInactive', true)}; 722 | bubb._element.addEventListener('mouseenter', hideOrKeep, false); 723 | 724 | }; 725 | 726 | var initBubb = function() { 727 | 728 | bubb._initialized = true; 729 | bubb.triggers = {}; 730 | 731 | setMethodProxies(); 732 | createBubbElements(); 733 | listenToBubbEvents(); 734 | 735 | }; 736 | 737 | var availableOptions = [ 738 | 'callback', 739 | 'hoverCallback', 740 | 'background', 741 | 'color', 742 | 'transitionOff', 743 | 'interactive', 744 | 'delay', 745 | 'width', 746 | 'fontSize', 747 | 'class', 748 | 'anchor', 749 | 'direction', 750 | 'borderRadius', 751 | 'autoHide', 752 | 'toggle', 753 | 'autoDirection' 754 | ]; 755 | 756 | // const isMobile = (typeof window.orientation !== "undefined") || ~window.navigator.userAgent.indexOf('IEMobile') ? true : false; 757 | 758 | typeof module !== 'undefined' && module.exports ? module.exports = bubb : window.bubb = bubb; 759 | 760 | })(window, document); 761 | -------------------------------------------------------------------------------- /dist/bubb.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/dist/bubb.min.js.gz -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | bubb.surge.sh 2 | -------------------------------------------------------------------------------- /docs/assets/bubb.min.css: -------------------------------------------------------------------------------- 1 | [bubb] { 2 | position: relative; } 3 | [bubb]:before, [bubb]:after { 4 | position: absolute; 5 | pointer-events: none; 6 | visibility: hidden; 7 | z-index: 1; 8 | bottom: 0; 9 | opacity: 0; } 10 | [bubb]:before { 11 | content: ''; 12 | width: 0; 13 | height: 0; 14 | border-bottom: 10px solid #444; 15 | border-right: 10px solid transparent; 16 | border-left: 10px solid transparent; } 17 | [bubb]:after { 18 | content: attr(bubb); 19 | padding: .75em .9em .85em; 20 | line-height: 1.1; 21 | font-size: 17px; 22 | text-align: center; 23 | border-radius: 5px; 24 | background-color: #444; 25 | color: #fff; 26 | min-width: 9em; 27 | text-rendering: optimizeLegibility; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-osx-font-smoothing: grayscale; 30 | -webkit-touch-callout: none; 31 | -webkit-user-select: none; 32 | -khtml-user-select: none; 33 | -moz-user-select: none; 34 | -ms-user-select: none; 35 | user-select: none; } 36 | [bubb]:hover:before, [bubb]:hover:after { 37 | transition: transform 0.35s cubic-bezier(0, 0, 0, 1), opacity 0.35s cubic-bezier(0, 0, 0, 1); 38 | visibility: visible; 39 | opacity: 1; } 40 | [bubb][still]:hover:before, [bubb][still]:hover:after { 41 | transition-duration: 0s; } 42 | [bubb][delay]:hover:before, [bubb][delay]:hover:after { 43 | transition-delay: 0.75s; } 44 | [bubb][round]:after { 45 | border-radius: 30px !important; } 46 | [bubb][round][north][left]:after, [bubb][round][east][right]:after { 47 | border-radius: 30px 30px 30px 0 !important; } 48 | [bubb][round][north][right]:after, [bubb][round][west][right]:after { 49 | border-radius: 30px 30px 0 30px !important; } 50 | [bubb][round][left]:not([east]):not([west]):not([north]):after, [bubb][round][east][left]:after { 51 | border-radius: 0 30px 30px 30px !important; } 52 | [bubb][round][right]:not([east]):not([west]):not([north]):after, [bubb][round][west][left]:after { 53 | border-radius: 30px 0 30px 30px !important; } 54 | [bubb][large]:after { 55 | font-size: 20px !important; } 56 | [bubb]:not([east]):not([west]):not([north]):before, [bubb]:not([east]):not([west]):not([north]):after { 57 | left: 50%; } 58 | [bubb]:not([east]):not([west]):not([north]):before { 59 | transform: translate(-50%, calc( 10px + .065rem + 4px + 20px)); } 60 | [bubb]:not([east]):not([west]):not([north]):after { 61 | transform: translate(-50%, calc( 100% + 10px + 4px + 20px)); } 62 | [bubb]:not([east]):not([west]):not([north]):hover:before { 63 | transform: translate(-50%, calc( 10px + .065rem + 4px)); } 64 | [bubb]:not([east]):not([west]):not([north]):hover:after { 65 | transform: translate(-50%, calc( 100% + 10px + 4px)); } 66 | [bubb]:not([east]):not([west]):not([north])[left]:before, [bubb]:not([east]):not([west]):not([north])[right]:before { 67 | transform: translate(0, calc( 10px + .065rem + 4px + 20px)); } 68 | [bubb]:not([east]):not([west]):not([north])[left]:after, [bubb]:not([east]):not([west]):not([north])[right]:after { 69 | transform: translate(0, calc( 100% + 10px + 4px + 20px)); } 70 | [bubb]:not([east]):not([west]):not([north])[left]:hover:before, [bubb]:not([east]):not([west]):not([north])[right]:hover:before { 71 | transform: translate(0, calc( 10px + .065rem + 4px)); } 72 | [bubb]:not([east]):not([west]):not([north])[left]:hover:after, [bubb]:not([east]):not([west]):not([north])[right]:hover:after { 73 | transform: translate(0, calc( 100% + 10px + 4px)); } 74 | [bubb]:not([east]):not([west]):not([north])[left]:before, [bubb]:not([east]):not([west]):not([north])[left]:after { 75 | left: 0; } 76 | [bubb]:not([east]):not([west]):not([north])[left]:before { 77 | border-left: none; } 78 | [bubb]:not([east]):not([west]):not([north])[left]:after { 79 | border-radius: 0 5px 5px 5px; } 80 | [bubb]:not([east]):not([west]):not([north])[right]:before, [bubb]:not([east]):not([west]):not([north])[right]:after { 81 | left: auto; 82 | right: 0; } 83 | [bubb]:not([east]):not([west]):not([north])[right]:before { 84 | border-right: none; } 85 | [bubb]:not([east]):not([west]):not([north])[right]:after { 86 | border-radius: 5px 0 5px 5px; } 87 | [bubb][north]:before, [bubb][north]:after { 88 | left: 50%; 89 | top: 0; 90 | bottom: auto; } 91 | [bubb][north]:before { 92 | border-bottom: none; 93 | border-top: 10px solid #444; 94 | border-left-color: transparent; 95 | border-right-color: transparent; 96 | transform: translate(-50%, calc( -10px - .065rem - 4px - 20px)); } 97 | [bubb][north]:after { 98 | transform: translate(-50%, calc( -100% - 10px - 4px - 20px)); } 99 | [bubb][north]:hover:before { 100 | transform: translate(-50%, calc( -10px - .065rem - 4px)); } 101 | [bubb][north]:hover:after { 102 | transform: translate(-50%, calc( -100% - 10px - 4px)); } 103 | [bubb][north][left]:before, [bubb][north][right]:before { 104 | transform: translate(0, calc( -10px - .065rem - 4px - 20px)); } 105 | [bubb][north][left]:after, [bubb][north][right]:after { 106 | transform: translate(0, calc( -100% - 10px - 4px - 20px)); } 107 | [bubb][north][left]:hover:before, [bubb][north][right]:hover:before { 108 | transform: translate(0, calc( -10px - .065rem - 4px)); } 109 | [bubb][north][left]:hover:after, [bubb][north][right]:hover:after { 110 | transform: translate(0, calc( -100% - 10px - 4px)); } 111 | [bubb][north][left]:before, [bubb][north][left]:after { 112 | left: 0; 113 | top: 0; 114 | bottom: auto; } 115 | [bubb][north][left]:before { 116 | border-left: none; } 117 | [bubb][north][left]:after { 118 | border-radius: 5px 5px 5px 0; } 119 | [bubb][north][right]:before, [bubb][north][right]:after { 120 | left: auto; 121 | right: 0; 122 | top: 0; 123 | bottom: auto; } 124 | [bubb][north][right]:before { 125 | border-right: none; } 126 | [bubb][north][right]:after { 127 | border-radius: 5px 5px 0 5px; } 128 | [bubb][east]:before, [bubb][east]:after { 129 | left: auto; 130 | right: 0; 131 | top: 50%; 132 | bottom: auto; } 133 | [bubb][east]:before { 134 | border-left: none; 135 | border-right-color: #444; } 136 | [bubb][east]:before { 137 | border-top: 10px solid transparent; 138 | border-bottom-color: transparent; 139 | transform: translate(calc( 10px + .065rem + 4px + 20px), -50%); } 140 | [bubb][east]:after { 141 | transform: translate(calc( 100% + 10px + 4px + 20px), -50%); } 142 | [bubb][east]:hover:before { 143 | transform: translate(calc( 10px + .065rem + 4px), -50%); } 144 | [bubb][east]:hover:after { 145 | transform: translate(calc( 100% + 10px + 4px), -50%); } 146 | [bubb][east][left]:before { 147 | border-top: none; 148 | border-bottom-color: transparent; 149 | transform: translate(calc( 10px + .065rem + 4px + 20px), 0); } 150 | [bubb][east][left]:after { 151 | border-radius: 0 5px 5px 5px; 152 | transform: translate(calc( 100% + 10px + 4px + 20px), 0); } 153 | [bubb][east][left]:hover:before { 154 | transform: translate(calc( 10px + .065rem + 4px), 0); } 155 | [bubb][east][left]:hover:after { 156 | transform: translate(calc( 100% + 10px + 4px), 0); } 157 | [bubb][east][right]:before { 158 | border-top: 10px solid transparent; 159 | border-bottom: none; 160 | transform: translate(calc( 10px + .065rem + 4px + 20px), -100%); } 161 | [bubb][east][right]:after { 162 | border-radius: 5px 5px 5px 0; 163 | transform: translate(calc( 100% + 10px + 4px + 20px), -100%); } 164 | [bubb][east][right]:hover:before { 165 | transform: translate(calc( 10px + .065rem + 4px), -100%); } 166 | [bubb][east][right]:hover:after { 167 | transform: translate(calc( 100% + 10px + 4px), -100%); } 168 | [bubb][west]:before, [bubb][west]:after { 169 | left: 0; 170 | right: auto; 171 | top: 50%; 172 | bottom: auto; } 173 | [bubb][west]:before { 174 | border-right: none; 175 | border-top: 10px solid transparent; 176 | border-bottom-color: transparent; 177 | border-left-color: #444; } 178 | [bubb][west]:before { 179 | transform: translate(calc( -10px - .065rem - 4px - 20px), -50%); } 180 | [bubb][west]:after { 181 | transform: translate(calc( -100% - 10px - 4px - 20px), -50%); } 182 | [bubb][west]:hover:before { 183 | transform: translate(calc( -10px - .065rem - 4px), -50%); } 184 | [bubb][west]:hover:after { 185 | transform: translate(calc( -100% - 10px - 4px), -50%); } 186 | [bubb][west][left]:before { 187 | border-top: none; 188 | transform: translate(calc( -10px - .065rem - 4px - 20px), 0); } 189 | [bubb][west][left]:after { 190 | border-radius: 5px 0 5px 5px; 191 | transform: translate(calc( -100% - 10px - 4px - 20px), 0); } 192 | [bubb][west][left]:hover:before { 193 | transform: translate(calc( -10px - .065rem - 4px), 0); } 194 | [bubb][west][left]:hover:after { 195 | transform: translate(calc( -100% - 10px - 4px), 0); } 196 | [bubb][west][right]:before { 197 | border-bottom: none; 198 | transform: translate(calc( -10px - .065rem - 4px - 20px), -100%); } 199 | [bubb][west][right]:after { 200 | border-radius: 5px 5px 0 5px; 201 | transform: translate(calc( -100% - 10px - 4px - 20px), -100%); } 202 | [bubb][west][right]:hover:before { 203 | transform: translate(calc( -10px - .065rem - 4px), -100%); } 204 | [bubb][west][right]:hover:after { 205 | transform: translate(calc( -100% - 10px - 4px), -100%); } 206 | -------------------------------------------------------------------------------- /docs/assets/demo.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @font-face { 3 | font-family: 'Walsheim'; 4 | font-weight: bold; 5 | font-style: bold; 6 | src: url("./fonts/gt-walsheim-bold-web.ttf"); } 7 | 8 | @font-face { 9 | font-family: 'Walsheim'; 10 | font-weight: normal; 11 | font-style: normal; 12 | src: url("./fonts/gt-walsheim-light-web.ttf"); } 13 | 14 | * { 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; } 18 | 19 | html { 20 | overflow-y: scroll; } 21 | 22 | body { 23 | padding: 0; 24 | margin: 0; 25 | background: #fff; 26 | color: #444; 27 | font-family: 'Walsheim', arial, sans-serif; 28 | font-weight: normal; 29 | font-size: 1.5em; 30 | line-height: 1; 31 | letter-spacing: .025rem; 32 | position: relative; 33 | min-height: 100vh; 34 | box-sizing: border-box; 35 | max-width: 100vw; 36 | overflow: hidden; } 37 | 38 | header { 39 | font-size: 1em; 40 | padding: 2.5em calc( (100vw - 720px) / 2); 41 | box-sizing: border-box; 42 | width: 100%; 43 | position: relative; 44 | display: block; 45 | background: #444; 46 | color: #fff; } 47 | header:last-of-type { 48 | background: #FFDEDE; 49 | color: #444; } 50 | header:last-of-type a { 51 | color: #444; } 52 | header:last-of-type a:hover { 53 | color: #4797B1; } 54 | header aside { 55 | margin-top: .75em; } 56 | header aside a { 57 | color: #F7F3CE; 58 | text-decoration: none; 59 | font-size: 50%; 60 | text-transform: uppercase; 61 | letter-spacing: 2px; 62 | margin: .5em .5em 0 0; } 63 | header aside a:hover { 64 | opacity: .75; } 65 | header aside a:before { 66 | content: ''; 67 | max-height: 0; 68 | overflow: hidden; 69 | display: block; } 70 | header aside a.inline:before { 71 | content: ' | '; 72 | max-height: none; 73 | display: inline-block; 74 | opacity: .35; 75 | margin-right: .75em; 76 | font-size: 150%; 77 | position: relative; 78 | top: 4px; } 79 | header aside a.first { 80 | display: block; 81 | padding-top: 1em; 82 | margin-left: 0; } 83 | header i { 84 | font-style: normal; 85 | font-size: 50%; 86 | opacity: .65; 87 | position: relative; 88 | margin-right: -.75em; } 89 | header i:first-of-type { 90 | top: -.85em; } 91 | header i:last-of-type { 92 | left: -1.1em; } 93 | header b { 94 | font-weight: bold; } 95 | header span { 96 | float: right; } 97 | header:after { 98 | content: none; 99 | position: absolute; 100 | width: 100vw; 101 | height: 7.3em; 102 | display: block; 103 | background: rgba(0, 0, 0, 0.035); 104 | top: -3.25em; 105 | left: -7.5vw; } 106 | 107 | code { 108 | font-size: 70%; 109 | overflow-x: scroll; } 110 | code::-webkit-scrollbar { 111 | width: 0px; 112 | background: transparent; } 113 | 114 | pre { 115 | font-size: 16px; 116 | opacity: .35; 117 | transition: opacity .25s linear; 118 | position: relative; 119 | z-index: 0; 120 | filter: grayscale(100%); } 121 | pre:hover { 122 | opacity: 1; 123 | filter: grayscale(0%); } 124 | 125 | .opaque pre { 126 | opacity: 1; 127 | filter: grayscale(0%); 128 | padding: 0; 129 | border: none; 130 | font-size: 18px; } 131 | 132 | section { 133 | position: relative; 134 | max-width: 720px; 135 | margin: 0 auto; 136 | padding: .5em 0 4em; 137 | box-sizing: border-box; 138 | display: block; 139 | width: 90%; } 140 | section:first-of-type { 141 | padding: 0 0 4em; } 142 | section:first-of-type .header { 143 | font-weight: bold; 144 | width: 100%; 145 | height: 6em; 146 | line-height: 6em; 147 | text-decoration: none; 148 | background: #F7F3CE; 149 | display: block; 150 | padding: 0 5%; 151 | box-sizing: border-box; 152 | text-transform: uppercase; 153 | letter-spacing: 2px; 154 | font-size: 11px; 155 | color: #444; 156 | margin-bottom: 4em; 157 | white-space: nowrap; } 158 | section > p { 159 | font-size: 70%; 160 | line-height: 1.3; 161 | color: #888; } 162 | section article { 163 | position: relative; } 164 | section article > p { 165 | font-size: 70%; 166 | line-height: 1.3; 167 | color: #888; } 168 | section article > span > div { 169 | display: inline-block; 170 | font-size: 95%; 171 | margin-top: 1em; 172 | padding-bottom: 2px; 173 | border-bottom: 2px dotted #fad; 174 | cursor: help; 175 | position: static; } 176 | section article > span > div:hover { 177 | border-color: transparent; } 178 | section article > span > i:after { 179 | content: '⟵ :hover'; 180 | display: inline-block; 181 | white-space: nowrap; 182 | border-bottom: none; 183 | margin-left: 12px; 184 | font-size: 75%; 185 | opacity: .35; } 186 | section article > span > i.click:after { 187 | content: '⟵ :click'; } 188 | section article > div { 189 | display: inline-block; 190 | font-size: 95%; 191 | margin-top: 1em; 192 | padding-bottom: 2px; 193 | border-bottom: 2px dotted #fad; 194 | cursor: help; 195 | position: static; } 196 | section article > div:hover { 197 | border-color: transparent; } 198 | section article > i:after { 199 | content: '⟵ :hover'; 200 | display: inline-block; 201 | white-space: nowrap; 202 | border-bottom: none; 203 | margin-left: 12px; 204 | font-size: 75%; 205 | opacity: .35; } 206 | section article > i.click:after { 207 | content: '⟵ :click'; } 208 | 209 | footer { 210 | background: #F7F3CE; 211 | display: block; 212 | position: relative; 213 | height: 4.5em; 214 | width: 100%; 215 | line-height: 4.5em; } 216 | footer a { 217 | position: absolute; 218 | top: 0; 219 | left: calc( (100vw - 720px) / 2); 220 | text-transform: uppercase; 221 | letter-spacing: 2px; 222 | font-size: 11px; 223 | text-decoration: none; 224 | font-weight: bold; 225 | color: #444; 226 | /* 227 | &:last-of-type { 228 | left: auto; 229 | right: calc( (100vw - 720px) / 2 ); 230 | &:before { 231 | content: 'Download Bubb on'; 232 | } 233 | } 234 | */ } 235 | footer a:before { 236 | display: inline-block; 237 | font-weight: normal; 238 | margin-right: 6px; 239 | content: 'By'; } 240 | 241 | #eventsDisplay { 242 | background: rgba(15, 241, 206, 0.7); 243 | padding: 0 7.5vw; 244 | box-sizing: border-box; 245 | line-height: 100vh; 246 | height: 100vh; 247 | width: 100vw; 248 | font-size: 65%; 249 | font-style: normal; 250 | text-transform: uppercase; 251 | text-align: center; 252 | letter-spacing: 2px; 253 | position: fixed; 254 | top: 0; 255 | right: 0; 256 | pointer-events: none; 257 | z-index: 9999999; } 258 | #eventsDisplay[color="0"] { 259 | background: rgba(255, 222, 222, 0.6); } 260 | #eventsDisplay[color="1"] { 261 | background: rgba(247, 243, 206, 0.6); } 262 | #eventsDisplay[color="2"] { 263 | background: rgba(197, 236, 190, 0.6); } 264 | #eventsDisplay[color="3"] { 265 | background: rgba(71, 151, 177, 0.6); } 266 | #eventsDisplay[color="4"] { 267 | background: rgba(84, 101, 107, 0.6); } 268 | #eventsDisplay:empty { 269 | visibility: hidden; } 270 | 271 | .wait { 272 | opacity: 0; 273 | transition: opacity .25s linear; } 274 | .done .wait { 275 | opacity: 1; } 276 | 277 | section ul { 278 | font-size: 70%; 279 | list-style: none; 280 | color: #888; 281 | padding-left: 50px; 282 | line-height: 1.2; 283 | padding-top: .75em; } 284 | section ul li { 285 | margin-bottom: inherit; 286 | position: relative; } 287 | section ul li:before { 288 | position: absolute; 289 | top: .6em; 290 | height: 1px; 291 | width: 35px; 292 | left: -50px; 293 | background: #888; 294 | display: block; 295 | content: ''; } 296 | section ul li u { 297 | text-decoration: none; 298 | display: block; 299 | font-weight: bold; 300 | padding-bottom: 1px; } 301 | section ul:last-of-type li u { 302 | display: inline-block; 303 | margin-right: .5em; } 304 | 305 | hr { 306 | margin: 2.5em 0 1em; 307 | opacity: .25; } 308 | section:last-of-type hr { 309 | margin: 2em 0 1em; } 310 | section:last-of-type hr:last-of-type { 311 | margin: 1.5em 0; } 312 | 313 | .bubble-bobble { 314 | width: 44px; 315 | height: 44px; 316 | background: url(/assets/images/bubble_bobble.png); 317 | background-repeat: no-repeat; 318 | background-size: 100%; 319 | position: fixed; 320 | bottom: 1em; 321 | right: 1em; } 322 | .bubble-bobble:hover { 323 | background-position: 0 100%; } 324 | 325 | ul.share-buttons { 326 | list-style: none; 327 | padding: 0; 328 | margin: .75em 0 0; } 329 | ul.share-buttons.device { 330 | background: #444; 331 | padding: 2em 0; 332 | text-align: center; } 333 | 334 | ul.share-buttons li { 335 | display: inline; } 336 | ul.share-buttons li:before { 337 | content: none; } 338 | ul.share-buttons li:hover img { 339 | opacity: .75; } 340 | ul.share-buttons li img { 341 | min-width: 32px; 342 | min-height: 32px; 343 | max-width: 32px; 344 | margin: 0 4px; 345 | display: inline-block; } 346 | 347 | ul.share-buttons .sr-only { 348 | position: absolute; 349 | clip: rect(1px 1px 1px 1px); 350 | clip: rect(1px, 1px, 1px, 1px); 351 | padding: 0; 352 | border: 0; 353 | height: 1px; 354 | width: 1px; 355 | overflow: hidden; } 356 | 357 | device { 358 | display: none; } 359 | 360 | #toggle { 361 | float: left; 362 | width: 50%; } 363 | 364 | #toggler { 365 | float: right; 366 | width: 50%; 367 | text-align: right; 368 | -webkit-touch-callout: none; 369 | -webkit-user-select: none; 370 | -khtml-user-select: none; 371 | -moz-user-select: none; 372 | -ms-user-select: none; 373 | user-select: none; } 374 | #toggler pre { 375 | visibility: hidden; } 376 | 377 | #added { 378 | display: block; 379 | width: 100%; 380 | clear: both; } 381 | 382 | #all-directions { 383 | position: absolute; 384 | top: 100px; 385 | right: 0; 386 | width: 80px; 387 | height: 78px; 388 | border: none; 389 | background: transparent; 390 | border-radius: 50%; 391 | background-image: url(images/cirque.svg); 392 | background-repeat: no-repeat; } 393 | #all-directions > div { 394 | display: inline-block; 395 | z-index: 1; 396 | position: absolute; 397 | width: auto; 398 | height: auto; 399 | cursor: crosshair; } 400 | #all-directions > div:after { 401 | content: ''; 402 | background: rgba(255, 255, 255, 0.8); 403 | min-width: 24px; 404 | min-height: 30px; 405 | position: absolute; 406 | transition: background .25s ease; } 407 | #all-directions > div:hover:after { 408 | background: rgba(255, 255, 255, 0); } 409 | #all-directions > div.n:nth-child(1) { 410 | top: 7.5%; 411 | left: 25%; } 412 | #all-directions > div.n:nth-child(1):after { 413 | transform: translate(-50%, -50%) rotate(-30deg); } 414 | #all-directions > div.n:nth-child(2) { 415 | z-index: 9; 416 | top: 0; 417 | left: 50%; } 418 | #all-directions > div.n:nth-child(2):after { 419 | transform: translate(-50%, -50%); } 420 | #all-directions > div.n:nth-child(3) { 421 | top: 7.5%; 422 | left: 75%; } 423 | #all-directions > div.n:nth-child(3):after { 424 | transform: translate(-50%, -50%) rotate(30deg); } 425 | #all-directions > div.e:nth-child(4) { 426 | right: 7.5%; 427 | top: 25%; } 428 | #all-directions > div.e:nth-child(4):after { 429 | transform: translate(-50%, -50%) rotate(60deg); } 430 | #all-directions > div.e:nth-child(5) { 431 | right: 0; 432 | top: 50%; } 433 | #all-directions > div.e:nth-child(5):after { 434 | transform: translate(-50%, -50%) rotate(90deg); } 435 | #all-directions > div.e:nth-child(6) { 436 | right: 7.5%; 437 | top: 75%; } 438 | #all-directions > div.e:nth-child(6):after { 439 | transform: translate(-50%, -50%) rotate(120deg); } 440 | #all-directions > div.s:nth-child(7) { 441 | bottom: 7.5%; 442 | left: 25%; } 443 | #all-directions > div.s:nth-child(7):after { 444 | transform: translate(-50%, -50%) rotate(30deg); } 445 | #all-directions > div.s:nth-child(8) { 446 | z-index: 9; 447 | bottom: 0; 448 | left: 50%; } 449 | #all-directions > div.s:nth-child(8):after { 450 | transform: translate(-50%, -50%); } 451 | #all-directions > div.s:nth-child(9) { 452 | bottom: 7.5%; 453 | left: 75%; } 454 | #all-directions > div.s:nth-child(9):after { 455 | transform: translate(-50%, -50%) rotate(-30deg); } 456 | #all-directions > div.w:nth-child(10) { 457 | left: 7.5%; 458 | top: 25%; } 459 | #all-directions > div.w:nth-child(10):after { 460 | transform: translate(-50%, -50%) rotate(120deg); } 461 | #all-directions > div.w:nth-child(11) { 462 | left: 0; 463 | top: 50%; } 464 | #all-directions > div.w:nth-child(11):after { 465 | transform: translate(-50%, -50%) rotate(90deg); } 466 | #all-directions > div.w:nth-child(12) { 467 | left: 7.5%; 468 | top: 75%; } 469 | #all-directions > div.w:nth-child(12):after { 470 | transform: translate(-50%, -50%) rotate(60deg); } 471 | #all-directions > span { 472 | position: absolute; 473 | width: 0; 474 | height: 0; 475 | background: pink; 476 | left: 50%; 477 | top: 50%; 478 | transform: rotate(0deg); 479 | transition: all .25s ease; 480 | opacity: 1; 481 | /* 482 | > i { 483 | position: absolute; 484 | width:0; 485 | height: 0; 486 | border-left: 8px solid transparent; 487 | border-right: 8px solid transparent; 488 | border-bottom: 20px solid #444; 489 | border-top: none; 490 | left: 50%; 491 | top: 50%; 492 | transform: translate(-50%, -60%); 493 | } 494 | */ } 495 | #all-directions > span > i img { 496 | position: absolute; 497 | width: 35px; 498 | height: 35px; 499 | left: 50%; 500 | top: 50%; 501 | transform: translate(-50%, -50%); } 502 | #all-directions:hover > span { 503 | opacity: 1; } 504 | 505 | /* 506 | --- bubb modifiers 507 | */ 508 | .bubb { 509 | -webkit-touch-callout: none; 510 | -webkit-user-select: none; 511 | -khtml-user-select: none; 512 | -moz-user-select: none; 513 | -ms-user-select: none; 514 | user-select: none; } 515 | .bubb p { 516 | padding: 0; 517 | margin: 0; } 518 | .bubb b { 519 | font-weight: bold; 520 | letter-spacing: 1px; 521 | font-size: 90%; } 522 | 523 | .bubb img { 524 | max-width: 100%; 525 | display: block; 526 | margin: 0 auto .5em; } 527 | 528 | .bubb-menu bubb-bobb { 529 | padding: 0 !important; 530 | background: #FFDEDE !important; 531 | border-bottom-color: #FFDEDE !important; 532 | color: #444 !important; } 533 | .bubb-menu bubb-bobb div { 534 | padding: .75em 0; 535 | text-transform: uppercase; 536 | letter-spacing: 2px; 537 | font-size: 12px; 538 | font-weight: bold; 539 | transition: background 0.3s cubic-bezier(0, 0, 0, 0.75); 540 | cursor: pointer; } 541 | .bubb-menu bubb-bobb div:first-child { 542 | padding-top: 1em; } 543 | .bubb-menu bubb-bobb div:hover { 544 | text-decoration: line-through; } 545 | .bubb-menu bubb-bobb div:nth-child(2) { 546 | background: #F7F3CE; 547 | color: #444; } 548 | .bubb-menu bubb-bobb div:nth-child(3) { 549 | background: #C5ECBE; 550 | color: #444; } 551 | .bubb-menu bubb-bobb div:nth-child(4) { 552 | background: #4797B1; 553 | color: #fff; } 554 | .bubb-menu bubb-bobb div:nth-child(5) { 555 | background: #54656b; 556 | border-radius: 0 0 4px 4px; 557 | color: #fff; } 558 | 559 | .tipcolor { 560 | border-bottom-color: #FFDEDE !important; } 561 | 562 | @media all and (max-width: 550px) { 563 | hide { 564 | display: none; } 565 | device { 566 | display: block; } 567 | body { 568 | font-size: 5vw; 569 | padding-bottom: 0; } 570 | header, footer { 571 | padding: 1.5em; } 572 | section { 573 | text-align: center; 574 | padding: 4em 1.5em; } 575 | section > span { 576 | display: block; } 577 | section i { 578 | display: none; } 579 | section:first-of-type .header { 580 | margin-bottom: 3em; 581 | position: relative; 582 | left: -5vw; 583 | width: 100vw; 584 | text-align: center; 585 | font-size: 50%; } 586 | section:first-of-type .header.download { 587 | background: #C5ECBE; 588 | margin-bottom: 0; } 589 | section:first-of-type .header.info { 590 | background: #FFDEDE; 591 | margin-bottom: 0; } 592 | section:first-of-type .header.image { 593 | height: auto; 594 | padding: 0; 595 | margin: 0; 596 | display: block; } 597 | section:last-of-type { 598 | padding: 1.35em 0; } 599 | section > p, section ul { 600 | font-size: 70%; } 601 | section ul { 602 | padding-left: 0; } 603 | section ul li:before { 604 | content: none; } 605 | footer { 606 | position: static; 607 | text-align: center; 608 | padding: 2em 0 4em; 609 | line-height: 1.2; } 610 | footer a { 611 | position: static; } 612 | header { 613 | font-size: 1.25em; } 614 | header aside a { 615 | line-height: 1.5; 616 | font-size: 35%; } 617 | header aside a.inline:before { 618 | content: ''; } 619 | header aside a.first { 620 | display: inline-block; } 621 | pre { 622 | display: none; } 623 | .opaque pre { 624 | display: block; 625 | text-align: left; 626 | opacity: 1; 627 | filter: none; 628 | font-size: 80%; 629 | padding: 1em 10% 0; } 630 | .bubble-bobble { 631 | width: 32px; 632 | height: 32px; 633 | background-size: 32px; } 634 | .bubble-bobble:hover { 635 | background-position: 0 -32px; } 636 | #all-directions { 637 | display: none; } } 638 | -------------------------------------------------------------------------------- /docs/assets/images/bubb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/bubb.gif -------------------------------------------------------------------------------- /docs/assets/images/bubb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/bubb.png -------------------------------------------------------------------------------- /docs/assets/images/bubb_720.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/bubb_720.gif -------------------------------------------------------------------------------- /docs/assets/images/bubble_bobble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/bubble_bobble.png -------------------------------------------------------------------------------- /docs/assets/images/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/circle.png -------------------------------------------------------------------------------- /docs/assets/images/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /docs/assets/images/cirque.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /docs/assets/images/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frdnrdb/bubb/ea009ac3037d000b9f5a6c4fd0d78d18b5708cec/docs/assets/images/ghost.png -------------------------------------------------------------------------------- /docs/assets/images/icons/color/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/assets/images/icons/color/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 17 | 18 | 20 | 22 | 25 | 26 | 27 | 30 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/images/icons/color/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/assets/images/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/assets/images/icons/fill/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/assets/images/icons/fill/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/assets/images/icons/fill/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/assets/images/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/assets/images/icons/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/assets/images/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bubb - Euphemism for a JS tooltip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | Bubb InfotipJS 31 | 37 |
38 | 39 |
40 | 41 | Bubb was built for desktop 42 | 43 | See project on Github 44 | 45 | ↓ Pure CSS version available further down 46 |

Minimal, non-dependent, non-fancy JS infotip. No CSS needed.

47 |

Infotip? Seriously? Isn't that just a euphemism for tooltip? It sure is.

48 |

Still, Bubb has a couple of pros to consider. 49 |

    50 |
  • Content by configEach instance's content is referenced from a single configuration object. Plain convenience, lucid maintenance.
  • 51 |
  • Context menusBubb can act as a lazy man's UI
  • 52 |
53 |

54 |
55 | 56 |
57 | 58 | 59 |
Basic
60 |
61 | 62 |
Menu
63 |
64 | 65 |
Callback on hover
66 |
67 | 68 |
Toggle
69 |
70 | 71 |
Toggle remote
72 |
73 | 74 | 75 |
API and options
76 | 77 | 78 | 79 | 80 |
Override styling
81 |

The content is targeted through bubb-content > div. 82 |
The trigger element gets className .bubb (and .bubb-menu) 83 |
the bubble tagname is bubb-bobb

84 | 85 |
86 | 87 |
88 | 89 | 90 | 91 |
92 | Bubb InfotipCSS 93 | 97 |
98 | 99 |
100 |
101 |

Pure CSS infotip, <div bubb="attribute only" />.

102 |
103 |
Default
104 |
Direction
105 |
Delay
106 |
No transitions
107 |
Delayed, rounded...
108 |
109 |

110 |

    111 |
  • bubbInitiate with attribute content. Default direction south
  • 112 |
  • nort, east, westChange direction
  • 113 |
  • left, rightAnchor the tip
  • 114 |
  • stillRemove the transition
  • 115 |
  • delayDelay the reveal
  • 116 |
  • roundAdd some border radius
  • 117 |
  • largeSubtly enlarge
  • 118 |
119 |

120 |
121 |
122 | 123 | 124 | 125 | 130 | 131 |
132 | frd 133 |
134 | 135 | 136 | 137 | 138 | 139 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/libs/css/tomorrow.min.css: -------------------------------------------------------------------------------- 1 | .hljs-comment,.hljs-quote{color:#8e908c}.hljs-variable,.hljs-template-variable,.hljs-tag,.hljs-name,.hljs-selector-id,.hljs-selector-class,.hljs-regexp,.hljs-deletion{color:#c82829}.hljs-number,.hljs-built_in,.hljs-builtin-name,.hljs-literal,.hljs-type,.hljs-params,.hljs-meta,.hljs-link{color:#f5871f}.hljs-attribute{color:#eab700}.hljs-string,.hljs-symbol,.hljs-bullet,.hljs-addition{color:#718c00}.hljs-title,.hljs-section{color:#4271ae}.hljs-keyword,.hljs-selector-tag{color:#8959a8}.hljs{display:block;overflow-x:auto;background:white;color:#4d4d4c;padding:0.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.registerTask('build', ['sass','cssmin','es6transpiler','uglify','copy:build','compress','replace','clean']); 6 | grunt.registerTask('serve:build', ['build','connect','open','watch']); 7 | grunt.registerTask('serve', ['sass','cssmin','es6transpiler','copy:dev','connect','open','watch']); 8 | grunt.registerTask('publish', ['build','shell:git_add','shell:git_commit','shell:npm_version','shell:git_push','shell:npm_publish','shell:surge']); 9 | 10 | grunt.initConfig ({ 11 | pkg: grunt.file.readJSON('package.json'), 12 | sass: { 13 | demo: { 14 | files: { 15 | 'scss/demo.css' : 'scss/demo.scss', 16 | 'scss/bubb.css' : 'scss/bubb.scss' 17 | } 18 | } 19 | }, 20 | cssnano: { 21 | dist: { 22 | files: { 23 | 'docs/assets/demo.min.css': 'scss/demo.css', 24 | 'docs/assets/bubb.min.css': 'scss/bubb.css' 25 | } 26 | } 27 | }, 28 | cssmin: { 29 | css: { 30 | files: [{ 31 | expand: true, 32 | cwd: 'scss/', 33 | src: ['*.css'], 34 | dest: 'docs/assets', 35 | ext: '.min.css' 36 | }] 37 | } 38 | }, 39 | es6transpiler: { 40 | main: { 41 | files: { 42 | 'js/script_transpiled.js': 'js/script.js', 43 | 'js/demo_transpiled.js': 'js/demo.js' 44 | } 45 | } 46 | }, 47 | uglify: { 48 | options: { 49 | banner: '/* ' + 50 | '<%= pkg.name %> v<%= pkg.version %> ' + 51 | '(<%= grunt.template.today("yyyy-mm-dd") %>) | ' + 52 | '<%= pkg.homepage %> | ' + 53 | '(c) <%= grunt.template.today("yyyy") %> <%= pkg.author %> | ' + 54 | 'licensed <%= pkg.license %> ' + 55 | '*/\n' 56 | }, 57 | main: { 58 | files: { 59 | 'docs/assets/demo.min.js': ['js/demo_transpiled.js'], 60 | 'docs/assets/bubb.min.js': ['js/script_transpiled.js'] 61 | } 62 | } 63 | }, 64 | copy: { 65 | build: { 66 | files: [ 67 | { 68 | src: 'docs/assets/bubb.min.js', 69 | dest: 'dist/bubb.min.js' 70 | }, 71 | { 72 | src: 'docs/assets/bubb.min.css', 73 | dest: 'dist/bubb.min.css' 74 | } 75 | ] 76 | }, 77 | dev: { 78 | files: [{ 79 | src: 'js/script_transpiled.js', 80 | dest: 'docs/assets/bubb.min.js' 81 | }, 82 | { 83 | src: 'js/demo_transpiled.js', 84 | dest: 'docs/assets/demo.min.js' 85 | }, 86 | { 87 | src: 'scss/demo.css', 88 | dest: 'docs/assets/demo.min.css' 89 | }, 90 | { 91 | src: 'scss/bubb.css', 92 | dest: 'docs/assets/bubb.min.css' 93 | }, 94 | { 95 | src: 'html/index.html', 96 | dest: 'docs/index.html' 97 | }] 98 | } 99 | }, 100 | replace: { 101 | html: { 102 | options: { 103 | patterns: [ 104 | { 105 | match: /<.+\sdelete\s.*\/.+>/g, 106 | replacement: '' 107 | }, 108 | { 109 | match: /{{filesize-(.+)}}/g, 110 | replacement: function (match, key) { 111 | var fs = require('fs'); 112 | function getFilesizeInBytes(filename) { 113 | const stats = fs.statSync(filename) 114 | const fileSizeInBytes = stats.size 115 | return (fileSizeInBytes/1024).toFixed(1)+' kB' 116 | } 117 | return getFilesizeInBytes('./dist/bubb.min.'+key); 118 | } 119 | }, 120 | ] 121 | }, 122 | files: [ 123 | {expand: true, flatten: true, src: ['html/index.html'], dest: 'docs/'} 124 | ] 125 | } 126 | }, 127 | clean: ['scss/style.css', 'scss/demo.css', 'scss/bubb.css', 'js/script_transpiled.js', 'js/demo_transpiled.js'], 128 | watch: { 129 | source: { 130 | files: ['scss/*.scss','html/index.html','js/script.js','js/demo.js'], 131 | tasks: ['sass','es6transpiler','copy','clean'], 132 | options: { 133 | livereload: true 134 | } 135 | } 136 | }, 137 | connect: { 138 | server: { 139 | options: { 140 | port: 5000, 141 | hostname: 'localhost', 142 | base: 'docs' 143 | } 144 | } 145 | }, 146 | open : { 147 | main: { 148 | path: 'http://localhost:5000', 149 | app: grunt.option('safari') ? 'Safari' : 'Google Chrome' 150 | } 151 | }, 152 | shell: { 153 | git_add: { 154 | command: 'git add .' 155 | }, 156 | git_commit: { 157 | command: 'git commit -m \'patch\'' 158 | }, 159 | git_push: { 160 | command: 'git push origin master' 161 | }, 162 | npm_version: { 163 | command: 'npm version patch' 164 | }, 165 | npm_publish: { 166 | command: 'npm publish' 167 | }, 168 | surge: { 169 | command: 'surge' 170 | } 171 | }, 172 | compress: { 173 | main: { 174 | options: { 175 | mode: 'gzip' 176 | }, 177 | files: [{ 178 | expand: true, 179 | cwd: 'dist/', 180 | src: ['bubb.min.css'], 181 | dest: 'dist/', 182 | extDot: 'last', 183 | ext: '.css.gz' 184 | }, 185 | { 186 | expand: true, 187 | cwd: 'dist/', 188 | src: ['bubb.min.js'], 189 | dest: 'dist/', 190 | extDot: 'last', 191 | ext: '.js.gz' 192 | }] 193 | } 194 | } 195 | }); 196 | 197 | }; 198 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bubb - Euphemism for a JS tooltip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | Bubb InfotipJS 31 | 37 |
38 | 39 |
40 | 41 | Bubb was built for desktop 42 | 43 | See project on Github 44 | 45 | ↓ Pure CSS version available further down 46 |

Minimal, non-dependent, non-fancy JS infotip. No CSS needed.

47 |

Infotip? Seriously? Isn't that just a euphemism for tooltip? It sure is.

48 |

Still, Bubb has a couple of pros to consider. 49 |

    50 |
  • Content by configEach instance's content is referenced from a single configuration object. Plain convenience, lucid maintenance.
  • 51 |
  • Context menusBubb can act as a lazy man's UI
  • 52 |
53 |

54 |
55 | 56 |
57 | 58 | 59 |
Basic
60 |
61 | 62 |
Menu
63 |
64 | 65 |
Callback on hover
66 |
67 | 68 |
Toggle
69 |
70 | 71 |
Toggle remote
72 |
73 | 74 | 75 |
API and options
76 | 77 | 78 | 79 | 80 |
Override styling
81 |

The content is targeted through bubb-content > div. 82 |
The trigger element gets className .bubb (and .bubb-menu) 83 |
the bubble tagname is bubb-bobb

84 | 85 |
86 | 87 |
88 | 89 | 90 | 91 |
92 | Bubb InfotipCSS 93 | 97 |
98 | 99 |
100 |
101 |

Pure CSS infotip, <div bubb="attribute only" />.

102 |
103 |
Default
104 |
Direction
105 |
Delay
106 |
No transitions
107 |
Delayed, rounded...
108 |
109 |

110 |

    111 |
  • bubbInitiate with attribute content. Default direction south
  • 112 |
  • nort, east, westChange direction
  • 113 |
  • left, rightAnchor the tip
  • 114 |
  • stillRemove the transition
  • 115 |
  • delayDelay the reveal
  • 116 |
  • roundAdd some border radius
  • 117 |
  • largeSubtly enlarge
  • 118 |
119 |

120 |
121 |
122 | 123 | 124 | 125 | 130 | 131 |
132 | frd 133 |
134 | 135 | 136 | 137 | 138 | 139 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /js/demo.js: -------------------------------------------------------------------------------- 1 | (function(bubb, hljs){ 2 | 3 | "use strict"; 4 | 5 | function render_code_blocks() { 6 | 7 | // render code blocks 8 | 9 | Array.from( document.getElementsByTagName('div') ).forEach( div => { 10 | 11 | let temp = document.createElement('temp'); 12 | temp.appendChild(div.cloneNode(true)); 13 | let pre = document.createElement('pre'), 14 | code = document.createElement('code'); 15 | pre.appendChild(code); 16 | code.innerHTML = temp.innerHTML.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\=""/g,''); 17 | 18 | div.parentNode.insertBefore(pre, div.nextSibling.nextSibling); 19 | 20 | }); 21 | 22 | document.querySelector('section').insertAdjacentHTML('beforeend', ` 23 | <div id="all-directions"> 24 | <div class="n" data-bubb="_nr"></div> 25 | <div class="n" data-bubb="_n"></div> 26 | <div class="n" data-bubb="_nl"></div> 27 | <div class="e" data-bubb="_er"></div> 28 | <div class="e" data-bubb="_e"></div> 29 | <div class="e" data-bubb="_el"></div> 30 | <div class="s" data-bubb="_sr"></div> 31 | <div class="s" data-bubb="_s"></div> 32 | <div class="s" data-bubb="_sl"></div> 33 | <div class="w" data-bubb="_wr"></div> 34 | <div class="w" data-bubb="_w"></div> 35 | <div class="w" data-bubb="_wl"></div> 36 | <span id="mouse-icon"><i><img src="data:image/svg+xml;base64, 37 | PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDY1IDQ2NSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDY1IDQ2NTsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBjbGFzcz0iIj48Zz48cGF0aCBkPSJNMzQ2LjczNiw0NC42MjNDMzIxLjQ5NCwxNS4wMTQsMjgzLjA2LDAsMjMyLjUsMHMtODguOTk0LDE1LjAxNC0xMTQuMjM2LDQ0LjYyM2MtMjUuMzgsMjkuNzcxLTI5LjE2OSw2NC42NS0yOS4xNjksODIuNzkyICB2MjEwLjE3MWMwLDE4LjE0MiwzLjc4OSw1My4wMjEsMjkuMTY5LDgyLjc5MkMxNDMuNTA2LDQ0OS45ODcsMTgxLjk0LDQ2NSwyMzIuNSw0NjVzODguOTk0LTE1LjAxMywxMTQuMjM2LTQ0LjYyMiAgYzI1LjM4LTI5Ljc3MSwyOS4xNjktNjQuNjUsMjkuMTY5LTgyLjc5MlYxMjcuNDE1QzM3NS45MDUsMTA5LjI3MywzNzIuMTE2LDc0LjM5NCwzNDYuNzM2LDQ0LjYyM3ogTTIzMi41LDE2MiAgYy0xMC40NzcsMC0xOS04LjUyMy0xOS0xOXYtNDAuNzE2YzAtMTAuNDc3LDguNTIzLTE5LDE5LTE5czE5LDguNTIzLDE5LDE5VjE0M0MyNTEuNSwxNTMuNDc3LDI0Mi45NzcsMTYyLDIzMi41LDE2MnogICBNMzYwLjkwNSwzMzcuNTg2YzAsMTguNzcxLTYuMTksMTEyLjQxNC0xMjguNDA1LDExMi40MTRzLTEyOC40MDUtOTMuNjQzLTEyOC40MDUtMTEyLjQxNFYxMjcuNDE1ICBjMC0xOC4zNzksNS45NTMtMTA4LjUxNiwxMjAuOTA1LTExMi4yNzl2NTMuOTkyYy0xNS4xNSwzLjQyNi0yNi41LDE2Ljk4NS0yNi41LDMzLjE1NlYxNDNjMCwxNi4xNzEsMTEuMzUsMjkuNzMsMjYuNSwzMy4xNTZ2NjEuNjI4ICBjMCw0LjE0MywzLjM1Nyw3LjUsNy41LDcuNXM3LjUtMy4zNTcsNy41LTcuNXYtNjEuNjI4YzE1LjE1LTMuNDI2LDI2LjUtMTYuOTg1LDI2LjUtMzMuMTU2di00MC43MTYgIGMwLTE2LjE3MS0xMS4zNS0yOS43My0yNi41LTMzLjE1NlYxNS4xMzZjMTE0Ljk1MywzLjc2NCwxMjAuOTA1LDkzLjksMTIwLjkwNSwxMTIuMjc5VjMzNy41ODZ6IiBkYXRhLW9yaWdpbmFsPSIjMDAwMDAwIiBjbGFzcz0iYWN0aXZlLXBhdGgiIHN0eWxlPSJmaWxsOiM0NDQ0NDQiIGRhdGEtb2xkX2NvbG9yPSIjNDc5N0IxIj48L3BhdGg+PC9nPiA8L3N2Zz4=" /></i></span> 38 | </div>`); // <span id="compass"><i></i></span> 39 | 40 | // timeout then hide menu event info 41 | 42 | let eventsDisplay = document.getElementById('eventsDisplay'); 43 | 44 | document.addEventListener('click', e => { 45 | if (e.target.id === 'eventsDisplay') return; 46 | clearTimeout(eventsDisplay.hideTimeout); 47 | eventsDisplay.hideTimeout = setTimeout(function() { 48 | eventsDisplay.innerHTML = ''; 49 | }, 1500); 50 | }); 51 | 52 | } 53 | 54 | function addElementsToDOM(markup) { 55 | document.querySelector('#added').insertAdjacentHTML('beforeend', markup + '<i></i><br>'); 56 | } 57 | 58 | function demo() { 59 | 60 | let config = { 61 | reference: 'Referenced content maintained in a <b>separate configuration object</b>', 62 | pj: { 63 | vedder: 'vocals', 64 | mccready: 'guitar', 65 | gossard: 'guitar', 66 | ament: 'bass' 67 | }, 68 | abbruzzese: { 69 | text: '', 70 | _: { 71 | callback: (key, item) => { 72 | item.innerHTML = 'Loading...'; 73 | setTimeout(function(){ 74 | item.innerHTML = designQuotes[Math.floor(Math.random() * designQuotes.length)]; 75 | }, 750); 76 | }, 77 | hoverCallback: true, 78 | interactive: false, 79 | transitionOff: true 80 | } 81 | }, 82 | toggle: { 83 | text: '<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNjAgNjAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDYwIDYwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIGNsYXNzPSIiPjxnPjxnPgoJPHBhdGggZD0iTTQyLDEySDE4QzguMDc1LDEyLDAsMjAuMDc1LDAsMzBzOC4wNzUsMTgsMTgsMThoMjRjOS45MjUsMCwxOC04LjA3NSwxOC0xOFM1MS45MjUsMTIsNDIsMTJ6IE00Miw0NkgxOCAgIEM5LjE3OCw0NiwyLDM4LjgyMiwyLDMwczcuMTc4LTE2LDE2LTE2aDI0YzguODIyLDAsMTYsNy4xNzgsMTYsMTZTNTAuODIyLDQ2LDQyLDQ2eiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojNEE5N0IwIiBkYXRhLW9sZF9jb2xvcj0iIzRhOTdiMCI+PC9wYXRoPgoJPHBhdGggZD0iTTE4LDE3Yy03LjE2OCwwLTEzLDUuODMyLTEzLDEzczUuODMyLDEzLDEzLDEzczEzLTUuODMyLDEzLTEzUzI1LjE2OCwxNywxOCwxN3ogTTE4LDQxYy02LjA2NSwwLTExLTQuOTM1LTExLTExICAgczQuOTM1LTExLDExLTExczExLDQuOTM1LDExLDExUzI0LjA2NSw0MSwxOCw0MXoiIGRhdGEtb3JpZ2luYWw9IiMwMDAwMDAiIGNsYXNzPSJhY3RpdmUtcGF0aCIgc3R5bGU9ImZpbGw6IzRBOTdCMCIgZGF0YS1vbGRfY29sb3I9IiM0YTk3YjAiPjwvcGF0aD4KCTxwYXRoIGQ9Ik00OC4yOTMsMjUuMjkzTDQxLDMyLjU4NmwtMy4yOTMtMy4yOTNjLTAuMzkxLTAuMzkxLTEuMDIzLTAuMzkxLTEuNDE0LDBzLTAuMzkxLDEuMDIzLDAsMS40MTRsNCw0ICAgQzQwLjQ4OCwzNC45MDIsNDAuNzQ0LDM1LDQxLDM1czAuNTEyLTAuMDk4LDAuNzA3LTAuMjkzbDgtOGMwLjM5MS0wLjM5MSwwLjM5MS0xLjAyMywwLTEuNDE0UzQ4LjY4NCwyNC45MDIsNDguMjkzLDI1LjI5M3oiIGRhdGEtb3JpZ2luYWw9IiMwMDAwMDAiIGNsYXNzPSJhY3RpdmUtcGF0aCIgc3R5bGU9ImZpbGw6IzRBOTdCMCIgZGF0YS1vbGRfY29sb3I9IiM0YTk3YjAiPjwvcGF0aD4KPC9nPjwvZz4gPC9zdmc+" style="width:80px;" />', 84 | _: { 85 | background: '#F7F3CE', 86 | toggle: true 87 | } 88 | }, 89 | "Interstellar hootchie kootchie": { 90 | text: '<img src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCA1MTEuOTk5IDUxMS45OTkiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMS45OTkgNTExLjk5OTsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0zNzYuNzMsMjAzLjgyOFYzOS4zMzVDMzc2LjczLDE3LjY0NiwzNTkuMDg0LDAsMzM3LjM5NiwwYy0yMS42ODksMC0zOS4zMzQsMTcuNjQ2LTM5LjMzNCwzOS4zMzV2OTcuMDA1ICAgIGMtNS43MTUtMy4yNC0xMi4zMS01LjEwMS0xOS4zMzUtNS4xMDFjLTExLjY0OSwwLTIyLjEyNiw1LjA5NC0yOS4zMzUsMTMuMTY2Yy03LjIwOS04LjA3MS0xNy42ODgtMTMuMTY1LTI5LjMzNi0xMy4xNjUgICAgYy03LjAyMywwLTEzLjYxOCwxLjg1OS0xOS4zMzIsNS4wOTlWODQuMDVjMC0yMS42ODktMTcuNjQ1LTM5LjMzNS0zOS4zMzUtMzkuMzM1Yy0yMS42ODgsMC0zOS4zMzQsMTcuNjQ2LTM5LjMzNCwzOS4zMzV2MTIwLjc4OSAgICB2OTguOGMwLDM4LjA4MSwxNi40MTgsNzMuNjUxLDQzLjUyMSw5OC41ODh2OTkuNzcyYzAsNS41MjMsNC40NzcsMTAsMTAsMTBoMTM5LjkwMmM1LjUyMywwLDEwLTQuNDc3LDEwLTEwVjQxOC4wMyAgICBjMzkuOTA4LTI0LjI2MSw2NC40NjYtNjcuNTQ0LDY0LjQ2Ni0xMTQuMzkydi03MC40MzdDMzg5Ljk0NCwyMjEuNTMxLDM4NC44MjksMjExLjAzNywzNzYuNzMsMjAzLjgyOHogTTMxOC4wNjIsMzkuMzM1ICAgIGMwLTEwLjY2MSw4LjY3NC0xOS4zMzUsMTkuMzM0LTE5LjMzNWMxMC42NiwwLDE5LjMzNCw4LjY3NCwxOS4zMzQsMTkuMzM1djE1NS4wMTNjLTEuOTk1LTAuMzEzLTQuMDM4LTAuNDc5LTYuMTE5LTAuNDc5aC0zMi41NDkgICAgVjM5LjMzNXogTTI1OS4zOTIsMTcwLjYxMmMwLTAuMDEzLDAuMDAyLTAuMDI1LDAuMDAyLTAuMDM4YzAtMC4wMDktMC4wMDEtMC4wMTctMC4wMDEtMC4wMjUgICAgYzAuMDE1LTEwLjY0OSw4LjY4MS0xOS4zMDksMTkuMzM0LTE5LjMwOWMxMC42NjEsMCwxOS4zMzUsOC42NzMsMTkuMzM1LDE5LjMzNHYyMy4yOTZoLTE5LjMzN2MtNy4wMjUsMC0xMy42MTksMS44Ni0xOS4zMzMsNS4xICAgIFYxNzAuNjEyeiBNMjAwLjcyNCwyMDkuNjk3di0zOS4xMjNjMC0xMC42Niw4LjY3NC0xOS4zMzIsMTkuMzM0LTE5LjMzMmMxMC42NTIsMCwxOS4zMiw4LjY1OSwxOS4zMzQsMTkuMzA3ICAgIGMwLDAuMDA4LDAsMC4wMTcsMCwwLjAyNXY2Mi42M2MwLDAuMDI2LDAuMDAyLDAuMDUxLDAuMDAyLDAuMDc3djI5Ljg4N2MwLDEwLjY2MS04LjY3NiwxOS4zMzMtMTkuMzM4LDE5LjMzMyAgICBjLTEwLjY2LDAtMTkuMzMyLTguNjczLTE5LjMzMi0xOS4zMzNWMjA5LjY5N3ogTTM2OS45NDQsMzAzLjYzN2MwLDQxLjUyNS0yMi42ODksNzkuNzc1LTU5LjIxNSw5OS44NDIgICAgYy0wLjAyLDAuMDExLTAuMDM4LDAuMDIzLTAuMDU4LDAuMDMzYy0wLjAwMSwwLjAwMS0wLjAwMiwwLjAwMS0wLjAwMiwwLjAwMWMtMC4wMDQsMC4wMDItMC4wMDYsMC4wMDQtMC4wMDksMC4wMDUgICAgYy0wLjI4MywwLjE1Ni0wLjU1OCwwLjMyNi0wLjgyMywwLjUwN2MtMC4xMTksMC4wODEtMC4yMjgsMC4xNzEtMC4zNDQsMC4yNTdjLTAuMTQyLDAuMTA2LTAuMjg2LDAuMjA5LTAuNDIyLDAuMzIzICAgIGMtMC4xNDgsMC4xMjQtMC4yODYsMC4yNTYtMC40MjYsMC4zODdjLTAuMDk0LDAuMDg4LTAuMTkxLDAuMTcyLTAuMjgxLDAuMjY0Yy0wLjE0MywwLjE0NC0wLjI3MywwLjI5NS0wLjQwNiwwLjQ0NiAgICBjLTAuMDgsMC4wOTItMC4xNjQsMC4xODEtMC4yNDEsMC4yNzdjLTAuMTE5LDAuMTQ2LTAuMjI4LDAuMjk5LTAuMzM3LDAuNDUxYy0wLjA4MiwwLjExMy0wLjE2NiwwLjIyNC0wLjI0NCwwLjM0ICAgIGMtMC4wODksMC4xMzYtMC4xNjksMC4yNzUtMC4yNTIsMC40MTVjLTAuMDg1LDAuMTQ0LTAuMTcyLDAuMjg3LTAuMjUsMC40MzVjLTAuMDYzLDAuMTE5LTAuMTE3LDAuMjQtMC4xNzQsMC4zNjEgICAgYy0wLjA4NCwwLjE3NS0wLjE2OCwwLjM0OS0wLjI0LDAuNTI5Yy0wLjA0NSwwLjEwNy0wLjA4LDAuMjE4LTAuMTIsMC4zMjdjLTAuMDcxLDAuMTk1LTAuMTQ0LDAuMzg5LTAuMjAyLDAuNTg4ICAgIGMtMC4wMzUsMC4xMTctMC4wNjEsMC4yMzYtMC4wOTIsMC4zNTRjLTAuMDQ5LDAuMTkyLTAuMTAxLDAuMzgzLTAuMTM5LDAuNTc5Yy0wLjAzMiwwLjE2NS0wLjA1MiwwLjMzMy0wLjA3NiwwLjUgICAgYy0wLjAyMSwwLjE1LTAuMDQ5LDAuMjk4LTAuMDY0LDAuNDUxYy0wLjAzLDAuMjk4LTAuMDQ0LDAuNTk5LTAuMDQ2LDAuOWMwLDAuMDI0LTAuMDA0LDAuMDQ3LTAuMDA0LDAuMDcxdjAuMDExICAgIGMwLDAuMDI0LDAsMC4wNDgsMCwwLjA3M3Y3OS42MzRIMTg1LjU3NXYtMTcuNTczaDQxLjAzNWM1LjUyMywwLDEwLTQuNDc3LDEwLTEwcy00LjQ3Ny0xMC0xMC0xMGgtNDEuMDM1di0zNi45ODkgICAgYzcuNzEzLDQuNzk5LDE1Ljk3MSw4Ljg3MywyNC43MTcsMTIuMDcxYzEuMTMzLDAuNDE1LDIuMjkzLDAuNjExLDMuNDM0LDAuNjExYzQuMDc4LDAsNy45MS0yLjUxNSw5LjM5My02LjU2OCAgICBjMS44OTYtNS4xODctMC43NzEtMTAuOTI5LTUuOTU3LTEyLjgyNmMtNDQuOTI0LTE2LjQyOC03NS4xMDctNTkuNDYzLTc1LjEwNy0xMDcuMDg2di05OC44Vjg0LjA1ICAgIGMwLTEwLjY2Miw4LjY3NC0xOS4zMzUsMTkuMzM2LTE5LjMzNWMxMC42NjEsMCwxOS4zMzMsOC42NzQsMTkuMzMzLDE5LjMzNXY4Ni41MjR2MzkuMTIzdjUzLjQ3MSAgICBjMCwyMS4xNDksMTYuNzgxLDM4LjQ0NiwzNy43MjcsMzkuMjkzYy0xLjYxOCwyLjU2LTMuMTUsNS4xOTQtNC41NjYsNy45MTVjLTIuNTQ5LDQuOS0wLjY0MywxMC45MzgsNC4yNTcsMTMuNDg2ICAgIGMxLjQ3NSwwLjc2NywzLjA1MiwxLjEzLDQuNjA2LDEuMTNjMy42MTEsMCw3LjA5OC0xLjk2Miw4Ljg4LTUuMzg3YzE1LjEwMi0yOS4wMzMsNDQuODEzLTQ3LjA2OCw3Ny41NDItNDcuMDY4aDEyLjEwOSAgICBjNS41MjMsMCwxMC00LjQ3NywxMC0xMHMtNC40NzctMTAtMTAtMTBoLTQyLjU1NGMtMTAuNjQ4LDAtMTkuMzExLTguNjUzLTE5LjMzMS0xOS4yOTd2LTAuMDM3YzAtMC4wMDctMC4wMDItMC4wMTQtMC4wMDItMC4wMjEgICAgYzAuMDExLTEwLjY1MSw4LjY3OS0xOS4zMTMsMTkuMzMzLTE5LjMxM2g3MS44ODZjMy44NSwwLDcuNDMxLDEuMTQ0LDEwLjQ0NywzLjA5YzAuMTQxLDAuMDk3LDAuMjg3LDAuMTg1LDAuNDM0LDAuMjc0ICAgIGM1LjA5OCwzLjQ4NCw4LjQ1Miw5LjM0LDguNDUyLDE1Ljk2N1YzMDMuNjM3eiIgZmlsbD0iIzQ3OTdiMSIvPgoJPC9nPgo8L2c+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTIxNS4zOTQsMzMyLjA5Yy01LjQyMy0xLjA3My0xMC42OCwyLjQ0NS0xMS43NTUsNy44NjJsLTAuMDQ5LDAuMjU3Yy0xLjAzNCw1LjQyNSwyLjUyNiwxMC42NjEsNy45NTIsMTEuNjk0ICAgIGMwLjYzMywwLjEyMSwxLjI2MiwwLjE3OSwxLjg4MywwLjE3OWM0LjcwNSwwLDguODk4LTMuMzM4LDkuODExLTguMTMxYzAuMDA0LTAuMDE5LDAuMDE3LTAuMDg4LDAuMDItMC4xMDYgICAgQzIyNC4zMzEsMzM4LjQyOCwyMjAuODExLDMzMy4xNjUsMjE1LjM5NCwzMzIuMDl6IiBmaWxsPSIjNDc5N2IxIi8+Cgk8L2c+CjwvZz4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMjU1LjU3OCw0NTQuNDI3aC0wLjIzOGMtNS41MjIsMC0xMCw0LjQ3Ny0xMCwxMHM0LjQ3OCwxMCwxMCwxMGgwLjIzOGM1LjUyMiwwLDEwLTQuNDc3LDEwLTEwICAgIFMyNjEuMTAxLDQ1NC40MjcsMjU1LjU3OCw0NTQuNDI3eiIgZmlsbD0iIzQ3OTdiMSIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=" />interstellar hootchie kootchie', 91 | _: { 92 | background: '#F7F3CE', 93 | color: '#4797B1' 94 | } 95 | }, 96 | _nl: { text: 'North, anchor left', _: { interactive: true, direction: 'north', anchor: 'left' } }, 97 | _n: { text: 'North', _: { interactive: true, direction: 'north' } }, 98 | _nr: { text: 'North, anchor right', _: { interactive: true, direction: 'north', anchor: 'right' } }, 99 | _el: { text: 'East, anchor left', _: { interactive: true, direction: 'east', anchor: 'left' } }, 100 | _e: { text: 'East', _: { interactive: true, direction: 'east' } }, 101 | _er: { text: 'East, anchor right', _: { interactive: true, direction: 'east', anchor: 'right' } }, 102 | _sl: { text: 'South, anchor left', _: { interactive: true, direction: 'south', anchor: 'left' } }, 103 | _s: { text: 'South', _: { interactive: true, direction: 'south' } }, 104 | _sr: { text: 'South, anchor right', _: { interactive: true, direction: 'south', anchor: 'right' } }, 105 | _wl: { text: 'West, anchor left', _: { interactive: true, direction: 'west', anchor: 'left' } }, 106 | _w: { text: 'West', _: { interactive: true, direction: 'west' } }, 107 | _wr: { text: 'West, anchor right', _: { interactive: true, direction: 'west', anchor: 'right' } }, 108 | }; 109 | 110 | bubb(config, (key, item) => { 111 | 112 | let eventsDisplay = document.getElementById('eventsDisplay'); 113 | eventsDisplay.innerHTML = 'clicked ' + key; 114 | eventsDisplay.setAttribute('color', Array.from(item.parentNode.children).indexOf(item)); 115 | 116 | console.log('clicked ' + key); 117 | 118 | }); 119 | 120 | document.querySelector('#toggle').addEventListener('click', function(){ 121 | bubb.toggle('toggle'); 122 | }, false); 123 | document.querySelector('#toggler').addEventListener('click', function(){ 124 | bubb.toggle('toggle'); 125 | }, false); 126 | 127 | bubb.update('pj.mccready', 'lead guitar'); 128 | bubb.add('pj.irons', 'drums'); 129 | bubb.update('abbruzzese', { background: '#FFDEDE', color: '#444' }); 130 | 131 | addElementsToDOM( '<div data-bubb="added_one">Insert method 1</div>' ); 132 | 133 | config.added_one = { 134 | text: 'config[reference] edited before refreshing bubb', 135 | _: { 136 | background: '#4797B1', 137 | color: '#fff', 138 | borderRadius: '14px', 139 | direction: 'north', 140 | anchor: 'left' 141 | } 142 | }; 143 | 144 | bubb.refresh(); // finds and adds new bubbs 145 | 146 | addElementsToDOM( '<div data-bubb="added_two">Insert method 2</div>' ); 147 | 148 | bubb.update('added_two', 'bubb.update(reference, content) called after adding bubb to DOM'); 149 | bubb.update('added_two', { 150 | width: 'section', 151 | anchor: 'left', 152 | fontSize: '23px', 153 | color: '#444', 154 | delay: 500, 155 | callback: true, 156 | class: 'tipcolor', 157 | background: 'repeating-linear-gradient(45deg, #FFDEDE, #FFDEDE 25%, #F7F3CE 25%, #F7F3CE 50%, #C5ECBE 50%, #C5ECBE 75%, #4797B1 75%, #4797B1 100%)' 158 | }); 159 | 160 | document.body.insertAdjacentHTML('beforeend', '<span data-bubb="bubble_bobble" class="bubble-bobble wait"></span>'); 161 | //bubb.refresh(); 162 | 163 | bubb.update('bubble_bobble',` 164 | <div>Share Bubb</div> 165 | <ul class="share-buttons"> 166 | <li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fbubb.surge.sh&text=Bubb%20-%20Euphemism%20for%20a%20JS%20tooltip:%20http%3A%2F%2Fbubb.surge.sh&via=frdnrdb" target="_blank" title="Tweet"><img alt="Tweet" src="/assets/images/icons/twitter.svg" /></a></li> 167 | <li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fbubb.surge.sh&title=Bubb%20-%20Euphemism%20for%20a%20JS%20tooltip" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="/assets/images/icons/reddit.svg" /></a></li> 168 | <li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fbubb.surge.sh&t=bubb" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="/assets/images/icons/facebook.svg" /></a></li> 169 | </ul>`); 170 | bubb.update('bubble_bobble', { 171 | direction: 'west', 172 | anchor: 'right', 173 | width: '200px', 174 | class: 'share-bubb', 175 | callback: function(){ 176 | console.log('thanks'); 177 | } 178 | }); 179 | 180 | } 181 | 182 | const display_methods = { 183 | 184 | target: '#methods', 185 | code:`// --> available methods 186 | 187 | bubb.refresh(); 188 | // initialize new data-bubb elements added to DOM 189 | 190 | bubb.update(reference, content | options); 191 | 192 | bubb.update(menu-reference, options); 193 | bubb.update(menu-reference.menu-item, content); 194 | 195 | bubb.add(menu-reference.menu-item, content); 196 | bubb.remove(menu-reference.menu-item); 197 | // these methods adds or removes DOM elements 198 | 199 | `}; 200 | 201 | const display_options_setup = { 202 | 203 | target: '#options_setup', 204 | code:`// --> options setup 205 | 206 | let config = { 207 | one: { 208 | text: 'content', 209 | _: { 210 | // ... one options 211 | } 212 | }, 213 | two: { 214 | menu_item_1: 'content', 215 | menu_item_2: 'content', 216 | _: { 217 | // ... two options 218 | } 219 | }, 220 | _: { 221 | // ... global options 222 | } 223 | }`}; 224 | 225 | const display_options = { 226 | 227 | target: '#options', 228 | code:`// --> available options 229 | 230 | callback: false 231 | // function(){} overrides initial (or global) callback 232 | // boolean true adds click listener and reports to default callback 233 | 234 | transitionOff: false 235 | // boolean 236 | 237 | interactive: false 238 | // boolean, default true for menus and option callback 239 | 240 | hoverCallback: false 241 | // boolean, trigger callback on element:hover 242 | 243 | delay: false 244 | // int value, microseconds reveal delay 245 | 246 | autoHide: false 247 | // int or milliseconds 248 | 249 | toggle: false 250 | // boolean, activate tooltip with function call bubb.toggle(key) 251 | 252 | direction: false 253 | // string 'north', 'west' or 'east' (default false = 'south') 254 | 255 | autoDirection: false 256 | // boolean, screen edge proximity aware direction change 257 | 258 | anchor: false 259 | // string 'left' or 'right' (default false = 'centered') 260 | 261 | width: false 262 | // int value <= 100 (document width percentage) 263 | // css string with units (eg. '300px') 264 | // querySelector string (eg. 'section:first-of-type') 265 | 266 | borderRadius: '4px' 267 | // css string with units 268 | 269 | fontSize: '17px' 270 | // css string with units 271 | 272 | background: '#444' 273 | // css color string 274 | 275 | color: '#fff' 276 | // css color string 277 | 278 | class: false 279 | // string, className to target current bubb specifically 280 | 281 | `}; 282 | 283 | const display_basic = { 284 | 285 | target: '#basic', 286 | code: `let config = { 287 | reference: 'Referenced content maintained in a <b>separate configuration object</b>' 288 | }; 289 | 290 | bubb(config);` 291 | 292 | } 293 | 294 | const display_menu = { 295 | 296 | target: '#menu', 297 | code: `let config = { 298 | pj: { 299 | vedder: 'vocals', 300 | mccready: 'guitar', 301 | gossard: 'guitar', 302 | ament: 'bass' 303 | }; 304 | 305 | bubb(config, (key, item) => { 306 | console.log('clicked ' + key); 307 | }); 308 | 309 | bubb.update('pj.mccready', 'lead guitar'); 310 | bubb.add('pj.irons', 'drums'); 311 | // bubb.remove('pj.vedder');` 312 | 313 | } 314 | 315 | const display_opts = { 316 | 317 | target: '#opts', 318 | code: `let config = { 319 | abbruzzese: { 320 | text: '', 321 | _: { 322 | callback: function(key, item) { 323 | item.innerHTML = 'Loading...'; 324 | setTimeout(function(){ // simulate async ajax 325 | item.innerHTML = designQuotes[Math.floor(Math.random() * designQuotes.length)]; 326 | }, 1000); 327 | }, 328 | hoverCallback: true, 329 | interactive: false, 330 | transitionOff: true 331 | } 332 | } 333 | }; 334 | 335 | bubb(config); 336 | 337 | bubb.update('abbruzzese', { background: '#fad', color: '#444' });` 338 | 339 | } 340 | 341 | const display_toggle = { 342 | 343 | target: '#toggle', 344 | code: `bubb({ 345 | toggle: { 346 | text: '<img src="toggleSwitch.png">', 347 | _: { 348 | background: '#F7F3CE', 349 | toggle: true 350 | } 351 | } 352 | }); 353 | 354 | someElement.addEventListener('click', 355 | () => bubb.toggle('toggle') ); 356 | ` 357 | 358 | } 359 | 360 | const display_added = { 361 | 362 | target: '#added', 363 | code: `// method 1: update main config then refresh bubb 364 | 365 | addElementToDOM( '<div data-bubb="added_one">Insert method 1</div>' ); // demo function 366 | 367 | config.added_one = { 368 | text: 'config[reference] edited before adding bubb to DOM', 369 | _: { 370 | background: '#4797B1', 371 | color: '#fff', 372 | borderRadius: '14px', 373 | direction: 'north', 374 | anchor: 'left' 375 | } 376 | }; 377 | 378 | bubb.refresh(); 379 | 380 | 381 | 382 | // method 2: use bubb update api 383 | 384 | addElementToDOM( '<div data-bubb="added_two">Insert method 2</div>' ); // demo function 385 | 386 | bubb.update('added_two', 'bubb.update(reference, content) called after adding bubb to DOM'); 387 | bubb.update('added_two', { 388 | width: 'section', 389 | anchor: 'left', 390 | fontSize: '23px', 391 | color: '#444', 392 | delay: 500, 393 | callback: true, 394 | class: 'tipcolor', 395 | background: 'repeating-linear-gradient(45deg, #FFDEDE, #FFDEDE 25%, #F7F3CE 25%, #F7F3CE 50%, #C5ECBE 50%, #C5ECBE 75%, #4797B1 75%, #4797B1 100%)' 396 | });` 397 | } 398 | 399 | const designQuotes = [ 400 | "<p>There are no bad ideas, just bad decisions.</p>", 401 | "<p>It&#8217;s not in the vulgar, it&#8217;s not in the shock that one finds art. And it&#8217;s not in the excessively beautiful. It&#8217;s in between; it&#8217;s in nuance. </p>", 402 | "<p>Don&#8217;t design for everyone. It&#8217;s impossible. All you end up doing is designing something that makes everyone unhappy. </p>", 403 | "<p>Designers deal in ideas. They give shape to ideas that shape our world, enrich everyday experiences, and improve our lives. Where there’s confusion, designers fashion clarity; where there’s chaos, designers construct order; where there’s entropy, designers promote vitality; where there’s indifference, designers swell passion; where there’s mediocrity, designers imbue excellence; and where there’s silence, designers lend voice. </p>", 404 | "<p>Stop downloading. Start uploading </p>", 405 | "<p>Good design is partially creativity and innovation, but primarily knowledge and awareness.</p>", 406 | "<p>Web design is responsive design. Responsive web design is web design, done right.</p>", 407 | "<p>As long as there are people, there will be user experience, and user interface designers.</p>", 408 | "<p>Well established hierarchies are not easily uprooted</p>", 409 | "<p>Everything is possible, that&#8217;s what science is all about. No, that&#8217;s what&#8217;s being a Magical Elf is all about. </p>", 410 | "<p>The designer is not always right. The researcher is not always wrong. Profit is not always the motive; market research, whatever its outcome, should never be used as a good excuse for bad design &#8211; in the same sense that good design should never be used to promote a bad product.</p>", 411 | "<p>You get up early in the morning and you work all day. That’s the only secret.</p>", 412 | "<p>[Designers&#8217;] primary competence lies not in the technicalities of a craft but in the mastery of a process.</p>", 413 | "<p>You have to finish things — that’s what you learn from, you learn by finishing things.</p>", 414 | "<p>A person tends to critique a design in one of several ways. The most common, and usually least valuable, is by gut reaction. </p>", 415 | "<p>You do a disservice to your clients when you <strong>don&#8217;t</strong> fire the bad ones because you eventually provide poor service to those you don&#8217;t want to serve. </p>", 416 | "<p>Think more, design less.</p>", 417 | "<p>Design is how you treat your customers. If you treat them well from an environmental, emotional, and aesthetic standpoint, you&#8217;re probably doing good design. </p>", 418 | "<p>Take a walk. Dance a jig. Get some sun. Don&#8217;t take yourself to serious. Cook something ethnic. Play the 3 chords you know on guitar. Go get coffee. Tell a bad joke, to yourself, and laugh. Look at the way a leaf is made. Overhear someone else&#8217;s conversation. Write it down. Remember it later. Get some sleep. </p>", 419 | "<p>Innovation is seldom hindered by platform.</p>" 420 | ]; 421 | 422 | function render_display_functions() { 423 | 424 | // display bubb js demo code 425 | 426 | function buildCodeBlock(from) { 427 | 428 | let pre = document.createElement('pre'), 429 | code = document.createElement('code'); 430 | pre.appendChild(code); 431 | code.innerHTML = from.toString().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\/\*\*\/[\s\S]*\/\*\*\//igm,''); 432 | 433 | return pre; 434 | 435 | } 436 | 437 | [display_basic, display_menu, display_opts, display_added, display_toggle, display_methods, display_options, display_options_setup].forEach( (obj, i) => { 438 | let block = buildCodeBlock(obj.code); 439 | document.querySelector(obj.target).appendChild(block); 440 | }); 441 | 442 | // colorful code 443 | 444 | hljs.initHighlightingOnLoad(); 445 | document.body.classList.add('done'); 446 | 447 | } 448 | 449 | /* 450 | 451 | // mouse pointer aware directional arrow 452 | 453 | function getAngle(cx, cy, ex, ey) { 454 | var dy = ey - cy; 455 | var dx = ex - cx; 456 | var theta = Math.atan2(dy, dx); 457 | theta *= 180 / Math.PI; 458 | return theta; 459 | } 460 | 461 | function compass() { 462 | 463 | let compass = document.querySelector('#all-directions'); 464 | let needle = document.querySelector('#compass'); 465 | 466 | let box = needle.getBoundingClientRect(); 467 | 468 | ['mousemove','mouseenter'].forEach( event => compass.addEventListener(event, function(e) { 469 | 470 | if (e.target.nodeName !== 'DIV') return; 471 | 472 | let angle = getAngle(box.left, box.top, e.clientX, e.clientY), 473 | angleStep = Math.ceil((angle+1) / 22.5) * 22.5; 474 | 475 | needle.style.transform = 'rotate(' + (angleStep + 67.5) + 'deg)'; 476 | 477 | }, false)); 478 | 479 | } 480 | 481 | */ 482 | 483 | render_code_blocks(); 484 | demo(); 485 | render_display_functions(); 486 | 487 | //compass(); 488 | 489 | })(window.bubb, window.hljs); 490 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | TODO! 4 | 5 | * IMPROVE add eventEmitter 6 | - https://www.sitepoint.com/nodejs-events-and-eventemitter/ 7 | - https://github.com/chrisdavies/eev 8 | 9 | * IMPROVE Implement some try/catch error handling and/or input type checks to prevent user input errors 10 | 11 | * ADD mobile version 12 | * ADD Implement dim-rest-of-page-option 13 | 14 | * TRY alternative tooltip layout (material-ui-dropdown-menu-ish) as option 15 | * TRY alternative theme (light (box-shadowed) or thin border) as option 16 | 17 | * CONSIDER Menu-style as part of package 18 | 19 | */ 20 | 21 | (function(window, document) { 22 | 23 | const bubb = (config, callback) => { 24 | 25 | bubb.config = bubb.config || (typeof config === 'object' ? config : {}); 26 | bubb.config._ = bubb.config._ || config._ || {}; 27 | bubb.callback = bubb.callback || (typeof bubb.config._.callback === 'function' && bubb.config._.callback) || (typeof callback === 'function' && callback); 28 | 29 | // class bubb indicates the element has already been initiated/ configured 30 | let bubbs = arguments[0] === 'update' ? [arguments[1]] : Array.from( document.querySelectorAll('[data-bubb]:not(.bubb)') ); 31 | 32 | if (!bubbs.length) return; 33 | 34 | !bubb._initialized && initBubb(); 35 | 36 | bubbs.forEach(buildElement); 37 | 38 | } 39 | 40 | const buildElement = _trigger => { 41 | 42 | let key = _trigger.dataset.bubb.trim(), 43 | data = bubb.config[key] || key, 44 | 45 | chck = typeof data === 'object', 46 | opts = chck && data._, // config contains options _ 47 | only = opts && Object.keys(data).length === 1, // options only, use key as content 48 | menu = chck && !data.hasOwnProperty('text'); // config indicates a menu 49 | 50 | only && (data.text = key); 51 | 52 | let props = !menu ? opts ? ['text'] : [false] : Object.keys(data), 53 | bindMenu = typeof bubb.callback === 'function' || typeof bubb.config._.callback === 'function', 54 | triggerPosition = window.getComputedStyle(_trigger).position; 55 | 56 | _trigger._bubb = { 57 | key: key, 58 | config: setElementConfiguration(opts), 59 | type: menu ? 'menu' : opts ? 'opts' : 'string', 60 | markup: props.reduce( buildElementMarkup.bind(this, key), '' ), 61 | bind: (menu && bindMenu) || (opts && (typeof opts.callback === 'function' || typeof opts.callback === 'boolean')) 62 | }; 63 | 64 | triggerPosition && !triggerPosition.match(/absolute|fixed|relative/) && (_trigger.style.position = 'relative'); 65 | 66 | _trigger.classList.add('bubb'); 67 | 68 | bubb.triggers[key] = _trigger; 69 | 70 | bindElement(_trigger); 71 | 72 | }; 73 | 74 | const setElementConfiguration = opts => { 75 | 76 | return availableOptions.reduce( (config, option) => { 77 | opts && opts.hasOwnProperty(option) ? config[option] = opts[option] 78 | : bubb.config._.hasOwnProperty(option) ? config[option] = bubb.config._[option] 79 | : false; 80 | return config; 81 | }, {}); 82 | 83 | }; 84 | 85 | const buildElementMarkup = (key, markup, prop) => { 86 | 87 | if (prop === '_') return markup; 88 | 89 | let content = prop ? bubb.config[key][prop] : bubb.config[key] || key, 90 | selector = key + (prop && prop !== 'text' ? '.' + prop : ''), 91 | attribute = ` data-bubb-value="${selector}"`; 92 | 93 | return markup += `<div ${attribute}><span>${content}</span></div>`; 94 | 95 | }; 96 | 97 | const bindElement = (_trigger) => { 98 | 99 | let bubbEvents = _trigger._bubb.config.toggle ? [] : ['mouseenter', 'mouseleave']; 100 | 101 | if (_trigger._bubb.bind) bubbEvents.push('mousedown'); 102 | 103 | bubbEvents.forEach( event => _trigger.addEventListener(event, eventHandler, false) ); 104 | 105 | }; 106 | 107 | const configureElement = _trigger => { 108 | 109 | let trigger = _trigger._bubb, 110 | bubble = bubb._element, 111 | config = bubble._config = trigger.config; 112 | 113 | bubble.style.visibility = 'hidden'; 114 | 115 | bubble._elementContent.innerHTML = trigger.markup; 116 | 117 | bubble._bind = (trigger.bind && (config.interactive !== false)) || config.toggle; 118 | 119 | bubble.className = config.class || ''; 120 | 121 | setWidth(config, _trigger, bubble); 122 | 123 | trigger.type === 'menu' && _trigger.classList.add('bubb-menu'); 124 | 125 | _trigger.appendChild(bubble); 126 | bubb._trigger = _trigger; 127 | bubb._visible = false; 128 | 129 | appendStyles(bubb._element, '_bubblePreactive'); 130 | 131 | }; 132 | 133 | const bubbShow = () => { 134 | 135 | appendStyles(bubb._element, '_bubbleActive'); 136 | bubb._visible = true; 137 | 138 | }; 139 | 140 | const bubbHide = (e) => { 141 | 142 | appendStyles(bubb._element, ['_bubbleInactive', '_bubblePreactive']); 143 | bubb._visible = false; 144 | 145 | }; 146 | 147 | const autoDirection = (e, target) => { 148 | 149 | if (!e) return; 150 | 151 | const h = bubb._element.offsetHeight || 150; 152 | const w = bubb._element.offsetWidth || 150; 153 | const d = target._bubb.config.direction; 154 | 155 | const rect = target.getBoundingClientRect(); 156 | 157 | const limits = { 158 | w: rect.left < w, 159 | e: rect.right > (window.innerWidth - w), 160 | n: rect.top < h, 161 | s: rect.bottom > (window.innerHeight - h) 162 | } 163 | 164 | if (d && (d === 'east' || d === 'west')) { 165 | bubb._autoDirection = limits.w ? 'east' : limits.e ? 'west' : false; 166 | bubb._autoAnchor = limits.n ? 'left' : limits.s ? 'right' : false; 167 | } 168 | else { 169 | bubb._autoDirection = limits.s ? 'north' : limits.n ? 'south' : false; 170 | bubb._autoAnchor = false; 171 | } 172 | 173 | return bubb._autoDirection; 174 | 175 | }; 176 | 177 | const eventHandler = function(e) { 178 | 179 | if (!this._bubb) return; 180 | 181 | // ---> configure (if autoDirection in config OR trigger new element) 182 | 183 | ( 184 | ((bubb.config._.autoDirection || this._bubb.config.autoDirection) 185 | && autoDirection(e, this) 186 | ) 187 | || (bubb._trigger !== this) 188 | ) 189 | && configureElement(this); 190 | 191 | // ---> reveal or hide 192 | 193 | window.clearTimeout(bubb._timerOn); 194 | window.clearTimeout(bubb._timerOff); 195 | 196 | e.type === 'mouseenter' 197 | ? bubb._timerOn = window.setTimeout( bubbShow, (this._bubb.config.delay | 0) || 0) 198 | : e.type !== 'mousedown' && bubbHide(e); 199 | 200 | if (this._bubb.config.autoHide) { 201 | bubb._timerOff = window.setTimeout( bubbHide, this._bubb.config.autoHide + (this._bubb.config.delay | 0) ); 202 | } 203 | 204 | // ---> leave 205 | 206 | if (!this._bubb.bind || e.type === 'mouseleave') return; 207 | 208 | // ---> callback 209 | 210 | let hover = this._bubb.config.hoverCallback, 211 | bubbvalue = hover ? this.dataset.bubb : e.target.dataset.bubbValue || e.target.parentNode.dataset.bubbValue || e.target.parentNode.parentNode.dataset.bubbValue; 212 | 213 | if (!bubbvalue) return; 214 | 215 | let thiscallback = (typeof this._bubb.config.callback === 'function' && this._bubb.config.callback) || bubb.callback, 216 | item = this.querySelector(`[data-bubb-value="${this.dataset.bubb}"]`) || e.target; 217 | 218 | thiscallback(bubbvalue, item, this, e.type); 219 | 220 | } 221 | 222 | const isTrigger = (node, check) => { 223 | 224 | if (!node) return; 225 | if (node._bubb && (check ? node === bubb._trigger : true)) return node; 226 | return isTrigger(node.parentNode); 227 | 228 | }; 229 | 230 | const clickOutside = function(e) { 231 | 232 | // _toggler initiated the toggle 233 | 234 | !bubb._toggler && (bubb._toggler = e.target); 235 | 236 | // avoid toggle initiator to negate the toggle 237 | 238 | if (bubb._toggler === e.target) return; 239 | 240 | // click inside; the caller is the toggled element 241 | 242 | if (isTrigger(e.target, bubb._trigger)) return; 243 | 244 | // release _toggler, remove event listener and hide bubb 245 | 246 | bubb._toggler = false; 247 | window.removeEventListener('click', clickOutside, false); 248 | eventHandler.call(bubb._trigger, {target: bubb._trigger, type: 'mouseleave'}); 249 | 250 | } 251 | 252 | const toggle = () => { 253 | 254 | let element = arguments[0], 255 | _trigger = (typeof element === 'object' && element) || bubb.triggers[element]; 256 | 257 | if (!_trigger || !_trigger._bubb.config.toggle) { 258 | console.error('bubb: trying to toggle a non-existing or non-toggled element'); 259 | return; 260 | } 261 | 262 | //_trigger !== bubb._trigger && configureElement(_trigger); 263 | 264 | window[ (bubb._visible ? 'remove' : 'add') + 'EventListener']('click', clickOutside, false); 265 | eventHandler.call(_trigger, {target: _trigger, type: bubb._visible ? 'mouseleave' : 'mouseenter'}); 266 | 267 | }; 268 | 269 | const update = () => { 270 | 271 | let key = arguments[0], contentOrConfig = arguments[1]; 272 | 273 | if (typeof key !== 'string' || !contentOrConfig) return; 274 | 275 | let updateOptions = typeof contentOrConfig === 'object', 276 | menu = key.split('.').reduce( (obj, val, i) => { 277 | obj[['key','val'][i]] = val; 278 | return obj; 279 | }, {}), 280 | _trigger = document.querySelector(`[data-bubb="${menu.key || key}"]`); 281 | 282 | if (!_trigger && !updateOptions) return; 283 | 284 | if (!_trigger) { 285 | console.error('bubb: trying to update a non-existing element'); 286 | return; 287 | } 288 | 289 | // update added DOM element - when bubb.refresh() has not been used 290 | 291 | if (!_trigger._bubb) bubb('update', _trigger); 292 | 293 | bubb._trigger = false; 294 | 295 | // update element 296 | 297 | if (!updateOptions) { 298 | _trigger._bubb.markup = _trigger._bubb.markup.replace(new RegExp(`<div\\s+data-bubb-value="${key}">.*?</div>`), `<div data-bubb-value="${key}"><span>${contentOrConfig}</span></div>`); 299 | return; 300 | } 301 | 302 | // update element config 303 | 304 | let bindDefault = typeof contentOrConfig.callback === 'boolean' && !_trigger._bubb.bind, 305 | bindSelf = typeof contentOrConfig.callback === 'function' && !_trigger._bubb.config.hasOwnProperty('callback'), 306 | bindHover = contentOrConfig.hoverCallback && !_trigger._bubb.config.hoverCallback; 307 | 308 | if (bindDefault || bindSelf || bindHover) _trigger._bubb.bind = true; 309 | 310 | if (bindDefault || bindSelf) _trigger.addEventListener( 'mousedown', eventHandler, false); 311 | 312 | Object.keys(contentOrConfig).forEach( updatedKey => { 313 | if (!~availableOptions.indexOf(updatedKey)) return; 314 | if (_trigger) _trigger._bubb.config[updatedKey] = contentOrConfig[updatedKey]; 315 | else bubb.config['_'][updatedKey] = contentOrConfig[updatedKey]; 316 | }); 317 | 318 | // update main config 319 | 320 | updateMainConfig(key, contentOrConfig, updateOptions, _trigger); 321 | 322 | }; 323 | 324 | const addOrRemove = () => { 325 | 326 | if (arguments.length === 0) { 327 | bubb(); 328 | return; 329 | } 330 | 331 | let key = arguments[0], value = arguments[1], 332 | menu = key.split('.').reduce( (obj, val, i) => { 333 | obj[['key','val'][i]] = val; 334 | return obj; 335 | }, {}); 336 | 337 | if (!menu.val || !bubb.config[menu.key]) return; 338 | 339 | // add menu item 340 | 341 | bubb._trigger = false; 342 | 343 | if (value && typeof value === 'string' && !bubb.config[menu.key][menu.val]) { 344 | 345 | bubb.config[menu.key][menu.val] = value; 346 | 347 | document.querySelector(`[data-bubb="${menu.key}"]`)._bubb.markup += `<div data-bubb-value="${key}"><span>${value}</span></div>`; 348 | return; 349 | 350 | } 351 | 352 | // remove menu item 353 | 354 | if (!value && bubb.config[menu.key][menu.val]) { 355 | 356 | delete bubb.config[menu.key][menu.val]; 357 | 358 | let _trigger = document.querySelector(`[data-bubb="${menu.key}"]`); 359 | _trigger._bubb.markup = _trigger._bubb.markup.replace(new RegExp(`<div\\s+data-bubb-value="${key}">.*?</div>`), ''); 360 | 361 | } 362 | 363 | }; 364 | 365 | const setWidth = (config, _trigger, _bubble) => { 366 | 367 | let input = config.width, 368 | anchor = config.anchor, 369 | direction = config.direction, 370 | sideways = direction === 'east' || direction === 'west'; 371 | 372 | // '300px', '3em' 373 | if (typeof input === 'string' && parseInt(input)) { 374 | _bubble.style.width = input; 375 | return; 376 | } 377 | 378 | // 33, '33' || 'section > div' 379 | let width = (input | 0) || document.querySelector(input); 380 | 381 | if (!width) { 382 | _bubble.style.width = '100%'; 383 | return; 384 | } 385 | 386 | let padding = 30, 387 | fill = typeof width === 'object', 388 | bodyw = document.body.offsetWidth, 389 | box = _trigger.getBoundingClientRect(), 390 | boxm = box.width/2, 391 | boxl = box.left, 392 | boxr = box.right, 393 | inputWidth = fill ? width.offsetWidth : (width === 100 ? bodyw - padding*2 : (width * bodyw) / 100); 394 | 395 | if (anchor || sideways) { 396 | 397 | let newWidth = !sideways && anchor 398 | ? anchor === 'left' 399 | ? bodyw - boxl - padding : anchor === 'right' 400 | ? bodyw - ( bodyw - boxr ) - padding 401 | : false 402 | : direction === 'east' 403 | ? bodyw - boxr - padding : direction === 'west' 404 | ? boxl - padding 405 | : false; 406 | 407 | if (newWidth) { 408 | _bubble.style.width = Math.min(inputWidth, newWidth) + 'px'; 409 | return; 410 | } 411 | 412 | } 413 | 414 | let newWidth = Math.min(inputWidth, ((boxl + boxm > bodyw/2 ? bodyw - boxr + boxm: boxl + boxm) - padding) * 2); 415 | 416 | _bubble.style.width = newWidth + 'px'; 417 | _bubble.style.left = (boxm - newWidth/2) + 'px'; 418 | _bubble._elementTip.style.left = newWidth/2 + 'px'; 419 | 420 | }; 421 | 422 | const updateMainConfig = (key, contentOrConfig, updateOptions, _trigger) => { 423 | 424 | let typeMenu = _trigger._bubb.type === 'menu', 425 | typeOptions = _trigger._bubb.type === 'opts', 426 | keyVal = ~key.indexOf('.') && key.split('.'), 427 | menu = keyVal ? keyVal.reduce( (obj, val, i) => { 428 | obj[['key','val'][i]] = val; 429 | return obj; 430 | }, {}) : {}; 431 | 432 | if (!typeMenu && !typeOptions && !updateOptions) { 433 | bubb.config[key] = contentOrConfig; 434 | return; 435 | } 436 | 437 | let prop = menu.key || key; 438 | 439 | if (!updateOptions) { 440 | bubb.config[prop][menu.val || 'text'] = contentOrConfig; 441 | return; 442 | } 443 | 444 | if (!typeMenu && !typeOptions) { 445 | bubb.config[prop] = { 446 | text: bubb.config[prop], 447 | _: contentOrConfig 448 | }; 449 | _trigger._bubb.type === 'opts'; 450 | return; 451 | } 452 | 453 | bubb.config[prop]['_'] = bubb.config[prop]['_'] || contentOrConfig; 454 | Object.assign(bubb.config[prop]['_'], contentOrConfig); 455 | 456 | }; 457 | 458 | const styleVariables = { 459 | tipsize: '12px', 460 | offset: '.15em', 461 | distance: '20px', 462 | easing: 'cubic-bezier(0,0,0,1)', 463 | duration: '.3s', 464 | background: '#444', 465 | color: '#fff', 466 | rounding: '4px', 467 | fontsize: '17px' 468 | }; 469 | 470 | const styles = { 471 | _bubble: { 472 | position: 'absolute', 473 | zIndex: '99', 474 | display: 'block', 475 | padding: '.75em .9em .85em', 476 | lineHeight: '1.1', 477 | textAlign: 'center', 478 | cursor: 'default', 479 | minWidth: '150px', 480 | width: '100%', 481 | boxSizing: 'border-box', 482 | textRendering: 'optimizeLegibility', 483 | WebkitFontSmoothing: 'antialiased', 484 | MozOsxFontSmoothing: 'grayscale', 485 | wordWrap: 'break-word', 486 | hyphens: 'auto', 487 | WebkitTouchCallout: 'none', 488 | WebkitUserSelect: 'none', 489 | KhtmlUserSelect: 'none', 490 | MozUserSelect: 'none', 491 | MsUserSelect: 'none', 492 | userSelect: 'none' 493 | }, 494 | _bubbleInactive: { 495 | visibility: 'hidden', 496 | pointerEvents: 'none', 497 | opacity: '0', 498 | }, 499 | _bubblePreactive: { 500 | background: styleVariables['background'], 501 | borderBottomColor: styleVariables['background'], 502 | color: styleVariables['color'], 503 | fontSize: styleVariables['fontsize'], 504 | }, 505 | _bubbleActive: { 506 | transitionProperty: 'opacity, transform', 507 | transitionDuration: styleVariables['duration'], 508 | transitionTimingFunction: styleVariables['easing'], 509 | pointerEvents: 'all', 510 | opacity: '1', 511 | visibility: 'visible' 512 | }, 513 | _bubbleInteractive: { 514 | position: 'absolute', 515 | zIndex: '-1', 516 | display: 'none', 517 | width: `calc( 100% + ( ${styleVariables['tipsize']} + ${styleVariables['offset']} ) * 2 )`, 518 | height: `calc( 100% + ( ${styleVariables['tipsize']} + ${styleVariables['offset']} ) * 2 )`, 519 | top: `calc( -1 * ( ${styleVariables['tipsize']} + ${styleVariables['offset']} ) )`, 520 | left: `calc( -1 * ( ${styleVariables['tipsize']} + ${styleVariables['offset']} ) )`, 521 | pointerEvents: 'all', 522 | background: 'transparent' 523 | }, 524 | _bubbleTip: { 525 | position: 'absolute', 526 | width: '0', 527 | height: '0', 528 | borderLeft: `${styleVariables['tipsize']} solid transparent`, 529 | borderRight: `${styleVariables['tipsize']} solid transparent`, 530 | borderBottomWidth: styleVariables['tipsize'], 531 | borderBottomStyle: 'solid', 532 | borderBottomColor: 'inherit' 533 | } 534 | }; 535 | 536 | const styleTransforms = { 537 | positive: { 538 | active: `calc( 100% + ${styleVariables['tipsize']} + ${styleVariables['offset']} )`, 539 | inactive: `calc( 100% + ${styleVariables['tipsize']} + ${styleVariables['offset']} + ${styleVariables['distance']} )` 540 | }, 541 | negative: { 542 | active: `calc( -100% - ${styleVariables['tipsize']} - ${styleVariables['offset']} )`, 543 | inactive: `calc( -100% - ${styleVariables['tipsize']} - ${styleVariables['offset']} - ${styleVariables['distance']} )` 544 | } 545 | }; 546 | 547 | const styleDirections = { 548 | x: { 549 | east: styleTransforms.positive, 550 | west: styleTransforms.negative 551 | }, 552 | y: { 553 | south: styleTransforms.positive, 554 | north: styleTransforms.negative, 555 | } 556 | }; 557 | 558 | const styleRoundings = { 559 | south: { 560 | left: [0,1,1,1], 561 | right: [1,0,1,1] 562 | }, 563 | north: { 564 | left: [1,1,1,0], 565 | right: [1,1,0,1] 566 | }, 567 | east: { 568 | left: [0,1,1,1], 569 | right: [1,1,1,0] 570 | }, 571 | west: { 572 | left: [1,0,1,1], 573 | right: [1,1,0,1] 574 | } 575 | }; 576 | 577 | const evalAnchor = (left, anchor) => anchor ? ( (anchor === 'left' && left) || (anchor === 'right' && !left) ? 0 : 'auto' ) : left ? '50%' : 'auto'; 578 | 579 | const stylePositions = { 580 | south: { 581 | left: evalAnchor.bind(this, true), 582 | right: evalAnchor.bind(this, false), 583 | top: (anchor, tip) => tip ? `calc( 2px - ${styleVariables['tipsize']} )` : 'auto', 584 | bottom: (anchor, tip) => tip ? 'auto' : 0 585 | }, 586 | north: { 587 | left: (anchor, tip) => evalAnchor(true, tip && anchor ? anchor === 'left' ? 'left' : 'right' : anchor), 588 | right: (anchor, tip) => evalAnchor(false, tip && anchor ? anchor === 'left' ? 'left' : 'right' : anchor), 589 | top: (anchor, tip) => tip ? 'auto' : 0, 590 | bottom: (anchor, tip) => tip ? `calc( 2px - ${styleVariables['tipsize']} )` : 'auto' 591 | }, 592 | east: { 593 | left: (anchor, tip) => tip ? `calc( 2px - ${styleVariables['tipsize']} )` : 'auto', 594 | right: (anchor, tip) => tip ? 'auto' : 0, 595 | top: (anchor, tip) => tip ? evalAnchor(true, anchor) : !anchor || (anchor === 'left') ? '50%' : 'auto', 596 | bottom: (anchor, tip) => tip ? evalAnchor(false, anchor) : anchor === 'right' ? '50%' : 'auto' 597 | }, 598 | west: { 599 | left: (anchor, tip) => tip ? 'auto' : 0, 600 | right: (anchor, tip) => tip ? `calc( 2px - ${styleVariables['tipsize']} )` : 'auto', 601 | top: (anchor, tip) => tip ? evalAnchor(true, anchor) : !anchor || (anchor === 'left') ? '50%' : 'auto', 602 | bottom: (anchor, tip) => tip ? evalAnchor(false, anchor) : anchor === 'right' ? '50%' : 'auto' 603 | } 604 | }; 605 | 606 | const tipTransforms = { 607 | south: { 608 | center: 'translate(-50%, 0)', 609 | left: 'translate(-25%, 50%) rotate(90deg)', 610 | right: 'translate(25%, 50%) rotate(-90deg)' 611 | }, 612 | north: { 613 | center: 'translate(-50%, 0) rotate(180deg)', 614 | left: 'translate(-25%, -50%) rotate(90deg)', 615 | right: 'translate(25%, -50%) rotate(-90deg)' 616 | }, 617 | east: { 618 | center: 'translate(-25%, -50%) rotate(-90deg)', 619 | left: 'translate(0, 0) rotate(180deg)', 620 | right: 'translate(0, 0)' 621 | }, 622 | west: { 623 | center: 'translate(25%, -50%) rotate(90deg)', 624 | left: 'translate(0, 0) rotate(180deg)', 625 | right: 'translate(0, 0)' 626 | } 627 | }; 628 | 629 | const setDirectionSpecificStyles = (element, config, activeOrInactive) => { 630 | 631 | let direction = bubb._autoDirection || config.direction || 'south', 632 | anchor = bubb._autoAnchor || config.anchor; 633 | 634 | const setBubbleTransform = xy => ( typeof styleDirections[xy][direction] === 'object' && styleDirections[xy][direction][activeOrInactive] ) || ( anchor ? '0' : '-50%' ); 635 | 636 | element.style.transform = `translate(${setBubbleTransform('x')}, ${setBubbleTransform('y')})`; 637 | 638 | if (activeOrInactive === 'active') return; 639 | 640 | element._elementInteractive.style.display = element._bind ? 'block' : 'none'; 641 | 642 | element._elementTip.style.transform = tipTransforms[direction][anchor || 'center']; 643 | 644 | ['left', 'right', 'top', 'bottom'].forEach( position => { 645 | element.style[position] = stylePositions[direction][position](anchor); 646 | element._elementTip.style[position] = stylePositions[direction][position](anchor, true); 647 | }); 648 | 649 | element.style.borderRadius = (styleRoundings[direction][anchor] || [1]).reduce( (str, chk) => { return str += (chk ? config.borderRadius || styleVariables['rounding'] : 0) + ' '; }, ''); 650 | 651 | }; 652 | 653 | const appendStyles = (element, keys, init) => { 654 | 655 | let config = element._config || {}; 656 | 657 | keys = typeof keys === 'string' ? [keys] : keys; 658 | 659 | keys.forEach( key => { 660 | 661 | let active = key === '_bubbleActive', 662 | preactive = key === '_bubblePreactive', 663 | 664 | still = active && config.transitionOff, 665 | background = preactive && config.background, 666 | color = preactive && config.color, 667 | fontsize = preactive && config.fontSize; 668 | 669 | Object.keys(styles[key]).forEach( style => { 670 | element.style[style] = init ? styles[key][style] 671 | : style === 'transitionDuration' && still ? '0s' 672 | : (style === 'background' || style === 'borderBottomColor') && background ? background 673 | : style === 'color' && color ? color 674 | : style === 'fontSize' && fontsize ? fontsize 675 | : styles[key][style]; 676 | }); 677 | 678 | if (!init && (active || preactive)) setDirectionSpecificStyles(element, config, active ? 'active' : 'inactive'); 679 | 680 | }); 681 | 682 | }; 683 | 684 | const setMethodProxies = () => { 685 | 686 | bubb.update = () => update.apply(this, arguments); 687 | bubb.add = bubb.refresh = bubb.remove = () => addOrRemove.apply(this, arguments); 688 | bubb.toggle = () => toggle.apply(this, arguments); 689 | 690 | }; 691 | 692 | const createBubbElements = () => { 693 | 694 | bubb._element = document.createElement('bubb-bobb'); 695 | 696 | let element = bubb._element, 697 | tagMap = { 698 | _elementInteractive: 'bubb-interactive', 699 | _elementTip: 'bubb-tip', 700 | _elementContent: 'bubb-content' 701 | }; 702 | 703 | for (let tag in tagMap) { 704 | element[tag] = document.createElement(tagMap[tag]); 705 | element.appendChild(element[tag]); 706 | } 707 | 708 | appendStyles(element, ['_bubble', '_bubbleInactive'], true); 709 | appendStyles(element._elementTip, '_bubbleTip', true); 710 | appendStyles(element._elementInteractive, '_bubbleInteractive', true); 711 | 712 | bubb._den = document.createElement('bubb-den'); 713 | bubb._den.style.display = 'none'; 714 | bubb._den.appendChild(element); 715 | 716 | document.body.appendChild(bubb._den); 717 | 718 | }; 719 | 720 | const listenToBubbEvents = () => { 721 | 722 | const hideOrKeep = () => bubb._element._bind || appendStyles(bubb._element, '_bubbleInactive', true); 723 | bubb._element.addEventListener('mouseenter', hideOrKeep, false); 724 | 725 | }; 726 | 727 | const initBubb = () => { 728 | 729 | bubb._initialized = true; 730 | bubb.triggers = {}; 731 | 732 | setMethodProxies(); 733 | createBubbElements(); 734 | listenToBubbEvents(); 735 | 736 | }; 737 | 738 | const availableOptions = [ 739 | 'callback', 740 | 'hoverCallback', 741 | 'background', 742 | 'color', 743 | 'transitionOff', 744 | 'interactive', 745 | 'delay', 746 | 'width', 747 | 'fontSize', 748 | 'class', 749 | 'anchor', 750 | 'direction', 751 | 'borderRadius', 752 | 'autoHide', 753 | 'toggle', 754 | 'autoDirection' 755 | ]; 756 | 757 | // const isMobile = (typeof window.orientation !== "undefined") || ~window.navigator.userAgent.indexOf('IEMobile') ? true : false; 758 | 759 | typeof module !== 'undefined' && module.exports ? module.exports = bubb : window.bubb = bubb; 760 | 761 | })(window, document); 762 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bubb", 3 | "description": "infotip", 4 | "version": "2.0.1", 5 | "author": "frdnrdb", 6 | "license": "MIT", 7 | "main": "./dist/bubb.min.js", 8 | "homepage": "http://bubb.surge.sh", 9 | "scripts": { 10 | "start": "grunt serve" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:frdnrdb/bubb.git" 15 | }, 16 | "dependencies": {}, 17 | "devDependencies": { 18 | "fs": "0.0.2", 19 | "grunt": "^1.0.1", 20 | "grunt-contrib-clean": "^1.0.0", 21 | "grunt-contrib-compress": "^1.4.3", 22 | "grunt-contrib-connect": "^1.0.2", 23 | "grunt-contrib-copy": "^1.0.0", 24 | "grunt-contrib-cssmin": "^1.0.1", 25 | "grunt-contrib-uglify": "^1.0.1", 26 | "grunt-contrib-watch": "^1.0.0", 27 | "grunt-cssnano": "^2.1.0", 28 | "grunt-es6-transpiler": "^1.0.2", 29 | "grunt-open": "^0.2.3", 30 | "grunt-replace": "^1.0.1", 31 | "grunt-run": "^0.7.0", 32 | "grunt-sass": "^1.2.0", 33 | "grunt-shell": "^2.1.0", 34 | "load-grunt-tasks": "^3.5.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scss/bubb.scss: -------------------------------------------------------------------------------- 1 | $bubb-tip-size: 10px; 2 | $bubb-offset: 4px; 3 | $bubb-transition-distance: 20px; 4 | $bubb-transition-expression: cubic-bezier(0,0,0,1); 5 | $bubb-transition-delay: .75s; 6 | $bubb-border-radius: 5px; 7 | $bubb-border-radius-round: 30px; 8 | $bubb-min-width: 9em; 9 | $bubb-font-size-large: 20px; 10 | $bubb-font-size: 17px; 11 | $bubb-background: #444; 12 | $bubb-color: #fff; 13 | 14 | @mixin bubbStyle() { 15 | padding: .75em .9em .85em; 16 | line-height: 1.1; 17 | font-size: $bubb-font-size; 18 | text-align: center; 19 | border-radius: $bubb-border-radius; 20 | background-color: $bubb-background; 21 | color: $bubb-color; 22 | min-width: $bubb-min-width; 23 | @include textRendering(); 24 | @include userSelectNone(); 25 | } 26 | @mixin bubbTip($width) { 27 | width: 0; 28 | height: 0; 29 | border-bottom: $width solid $bubb-background; 30 | border-right: $width solid transparent; 31 | border-left: $width solid transparent; 32 | } 33 | @mixin bubbTransition() { 34 | transition: transform .35s $bubb-transition-expression, opacity .35s $bubb-transition-expression; 35 | } 36 | @mixin textRendering() { 37 | text-rendering: optimizeLegibility; 38 | -webkit-font-smoothing: antialiased; 39 | -moz-osx-font-smoothing: grayscale; 40 | } 41 | @mixin userSelectNone() { 42 | -webkit-touch-callout: none; 43 | -webkit-user-select: none; 44 | -khtml-user-select: none; 45 | -moz-user-select: none; 46 | -ms-user-select: none; 47 | user-select: none; 48 | } 49 | 50 | 51 | // bubb bottom, bubb east 52 | $y-transform-tip: calc( #{$bubb-tip-size} + .065rem + #{$bubb-offset} + #{$bubb-transition-distance} ); 53 | $y-transform-hover-tip: calc( #{$bubb-tip-size} + .065rem + #{$bubb-offset} ); 54 | $y-transform: calc( 100% + #{$bubb-tip-size} + #{$bubb-offset} + #{$bubb-transition-distance} ); 55 | $y-transform-hover: calc( 100% + #{$bubb-tip-size} + #{$bubb-offset} ); 56 | 57 | // bubb top, bubb west 58 | $y-transform-tip-top: calc( -#{$bubb-tip-size} - .065rem - #{$bubb-offset} - #{$bubb-transition-distance} ); 59 | $y-transform-hover-tip-top: calc( -#{$bubb-tip-size} - .065rem - #{$bubb-offset} ); 60 | $y-transform-top: calc( -100% - #{$bubb-tip-size} - #{$bubb-offset} - #{$bubb-transition-distance} ); 61 | $y-transform-hover-top: calc( -100% - #{$bubb-tip-size} - #{$bubb-offset} ); 62 | 63 | @mixin bubb-base() { 64 | position: relative; 65 | &:before, &:after { 66 | position: absolute; 67 | pointer-events: none; 68 | visibility: hidden; 69 | z-index: 1; 70 | bottom: 0; 71 | opacity: 0; 72 | } 73 | &:before { 74 | content: ''; 75 | @include bubbTip($bubb-tip-size); 76 | } 77 | &:after { 78 | content: attr(bubb); 79 | @include bubbStyle(); 80 | } 81 | &:hover { 82 | &:before, &:after { 83 | @include bubbTransition(); 84 | visibility: visible; 85 | opacity: 1; 86 | } 87 | } 88 | } 89 | @mixin bubb-center() { 90 | &:before, &:after { 91 | left: 50%; 92 | } 93 | &:before { 94 | transform: translate(-50%, $y-transform-tip); 95 | } 96 | &:after { 97 | transform: translate(-50%, $y-transform); 98 | } 99 | &:hover { 100 | &:before { 101 | transform: translate(-50%, $y-transform-hover-tip); 102 | } 103 | &:after { 104 | transform: translate(-50%, $y-transform-hover); 105 | } 106 | } 107 | } 108 | @mixin bubb-left-and-right() { 109 | &:before { 110 | transform: translate(0, $y-transform-tip); 111 | } 112 | &:after { 113 | transform: translate(0, $y-transform); 114 | } 115 | &:hover { 116 | &:before { 117 | transform: translate(0, $y-transform-hover-tip); 118 | } 119 | &:after { 120 | transform: translate(0, $y-transform-hover); 121 | } 122 | } 123 | } 124 | @mixin bubb-left() { 125 | &:before, &:after { 126 | left: 0; 127 | } 128 | &:before { 129 | border-left: none; 130 | } 131 | &:after { 132 | border-radius: 0 $bubb-border-radius $bubb-border-radius $bubb-border-radius; 133 | } 134 | } 135 | @mixin bubb-right() { 136 | &:before, &:after { 137 | left: auto; 138 | right: 0; 139 | } 140 | &:before { 141 | border-right: none; 142 | } 143 | &:after { 144 | border-radius: $bubb-border-radius 0 $bubb-border-radius $bubb-border-radius; 145 | } 146 | } 147 | @mixin bubb-east-all() { 148 | &:before, &:after { 149 | left: auto; 150 | right: 0; 151 | top: 50%; 152 | bottom: auto; 153 | } 154 | &:before { 155 | border-left: none; 156 | border-right-color: $bubb-background; 157 | } 158 | } 159 | @mixin bubb-east() { 160 | &:before { 161 | border-top: $bubb-tip-size solid transparent; 162 | border-bottom-color: transparent; 163 | transform: translate($y-transform-tip, -50%); 164 | } 165 | &:after { 166 | transform: translate($y-transform, -50%); 167 | } 168 | &:hover { 169 | &:before { 170 | transform: translate($y-transform-hover-tip, -50%); 171 | } 172 | &:after { 173 | transform: translate($y-transform-hover, -50%); 174 | } 175 | } 176 | } 177 | @mixin bubb-east-top() { 178 | &:before { 179 | border-top: none; 180 | border-bottom-color: transparent; 181 | transform: translate($y-transform-tip, 0); 182 | } 183 | &:after { 184 | border-radius: 0 $bubb-border-radius $bubb-border-radius $bubb-border-radius; 185 | transform: translate($y-transform, 0); 186 | } 187 | &:hover { 188 | &:before { 189 | transform: translate($y-transform-hover-tip, 0); 190 | } 191 | &:after { 192 | transform: translate($y-transform-hover, 0); 193 | } 194 | } 195 | } 196 | @mixin bubb-east-bottom() { 197 | &:before { 198 | border-top: $bubb-tip-size solid transparent; 199 | border-bottom: none; 200 | transform: translate($y-transform-tip, -100%); 201 | } 202 | &:after { 203 | border-radius: $bubb-border-radius $bubb-border-radius $bubb-border-radius 0; 204 | transform: translate($y-transform, -100%); 205 | } 206 | &:hover { 207 | &:before { 208 | transform: translate($y-transform-hover-tip, -100%); 209 | } 210 | &:after { 211 | transform: translate($y-transform-hover, -100%); 212 | } 213 | } 214 | } 215 | @mixin bubb-west-all() { 216 | &:before, &:after { 217 | left: 0; 218 | right: auto; 219 | top: 50%; 220 | bottom: auto; 221 | } 222 | &:before { 223 | border-right: none; 224 | border-top: $bubb-tip-size solid transparent; 225 | border-bottom-color: transparent; 226 | border-left-color: $bubb-background; 227 | } 228 | } 229 | @mixin bubb-west() { 230 | &:before { 231 | transform: translate($y-transform-tip-top, -50%); 232 | } 233 | &:after { 234 | transform: translate($y-transform-top, -50%); 235 | } 236 | &:hover { 237 | &:before { 238 | transform: translate($y-transform-hover-tip-top, -50%); 239 | } 240 | &:after { 241 | transform: translate($y-transform-hover-top, -50%); 242 | } 243 | } 244 | } 245 | @mixin bubb-west-top() { 246 | &:before { 247 | border-top: none; 248 | transform: translate($y-transform-tip-top, 0); 249 | } 250 | &:after { 251 | border-radius: $bubb-border-radius 0 $bubb-border-radius $bubb-border-radius; 252 | transform: translate($y-transform-top, 0); 253 | } 254 | &:hover { 255 | &:before { 256 | transform: translate($y-transform-hover-tip-top, 0); 257 | } 258 | &:after { 259 | transform: translate($y-transform-hover-top, 0); 260 | } 261 | } 262 | } 263 | @mixin bubb-west-bottom() { 264 | &:before { 265 | border-bottom: none; 266 | transform: translate($y-transform-tip-top, -100%); 267 | } 268 | &:after { 269 | border-radius: $bubb-border-radius $bubb-border-radius 0 $bubb-border-radius; 270 | transform: translate($y-transform-top, -100%); 271 | } 272 | &:hover { 273 | &:before { 274 | transform: translate($y-transform-hover-tip-top, -100%); 275 | } 276 | &:after { 277 | transform: translate($y-transform-hover-top, -100%); 278 | } 279 | } 280 | } 281 | @mixin bubb-north() { 282 | &:before, &:after { 283 | left: 50%; 284 | top: 0; 285 | bottom: auto; 286 | } 287 | &:before { 288 | border-bottom: none; 289 | border-top: $bubb-tip-size solid $bubb-background; 290 | border-left-color: transparent; 291 | border-right-color: transparent; 292 | transform: translate(-50%, $y-transform-tip-top); 293 | } 294 | &:after { 295 | transform: translate(-50%, $y-transform-top); 296 | } 297 | &:hover { 298 | &:before { 299 | transform: translate(-50%, $y-transform-hover-tip-top); 300 | } 301 | &:after { 302 | transform: translate(-50%, $y-transform-hover-top); 303 | } 304 | } 305 | } 306 | @mixin bubb-north-left-and-right() { 307 | &:before { 308 | transform: translate(0, $y-transform-tip-top); 309 | } 310 | &:after { 311 | transform: translate(0, $y-transform-top); 312 | } 313 | &:hover { 314 | &:before { 315 | transform: translate(0, $y-transform-hover-tip-top); 316 | } 317 | &:after { 318 | transform: translate(0, $y-transform-hover-top); 319 | } 320 | } 321 | } 322 | @mixin bubb-north-left() { 323 | &:before, &:after { 324 | left: 0; 325 | top: 0; 326 | bottom: auto; 327 | } 328 | &:before { 329 | border-left: none; 330 | } 331 | &:after { 332 | border-radius: $bubb-border-radius $bubb-border-radius $bubb-border-radius 0; 333 | } 334 | } 335 | @mixin bubb-north-right() { 336 | &:before, &:after { 337 | left: auto; 338 | right: 0; 339 | top: 0; 340 | bottom: auto; 341 | } 342 | &:before { 343 | border-right: none; 344 | } 345 | &:after { 346 | border-radius: $bubb-border-radius $bubb-border-radius 0 $bubb-border-radius; 347 | } 348 | } 349 | @mixin bubb-still() { 350 | &:hover { 351 | &:before, &:after { 352 | transition-duration: 0s; 353 | } 354 | } 355 | } 356 | @mixin bubb-delay() { 357 | &:hover { 358 | &:before, &:after { 359 | transition-delay: $bubb-transition-delay; 360 | } 361 | } 362 | } 363 | @mixin bubb-round() { 364 | &:after { 365 | border-radius: $bubb-border-radius-round !important; 366 | } 367 | &[north][left], &[east][right] { 368 | &:after { 369 | border-radius: $bubb-border-radius-round $bubb-border-radius-round $bubb-border-radius-round 0 !important; 370 | } 371 | } 372 | &[north][right], &[west][right] { 373 | &:after { 374 | border-radius: $bubb-border-radius-round $bubb-border-radius-round 0 $bubb-border-radius-round !important; 375 | } 376 | } 377 | &[left]:not([east]):not([west]):not([north]), &[east][left] { 378 | &:after { 379 | border-radius: 0 $bubb-border-radius-round $bubb-border-radius-round $bubb-border-radius-round !important; 380 | } 381 | } 382 | &[right]:not([east]):not([west]):not([north]), &[west][left] { 383 | &:after { 384 | border-radius: $bubb-border-radius-round 0 $bubb-border-radius-round $bubb-border-radius-round !important; 385 | } 386 | } 387 | } 388 | @mixin bubb-large() { 389 | &:after { 390 | font-size: $bubb-font-size-large !important; 391 | } 392 | } 393 | 394 | [bubb] { 395 | @include bubb-base(); 396 | &[still] { 397 | @include bubb-still(); 398 | } 399 | &[delay] { 400 | @include bubb-delay(); 401 | } 402 | &[round] { 403 | @include bubb-round(); 404 | } 405 | &[large] { 406 | @include bubb-large(); 407 | } 408 | &:not([east]):not([west]):not([north]) { 409 | @include bubb-center(); 410 | &[left], &[right] { 411 | @include bubb-left-and-right(); 412 | } 413 | &[left] { 414 | @include bubb-left(); 415 | } 416 | &[right] { 417 | @include bubb-right(); 418 | } 419 | } 420 | &[north] { 421 | @include bubb-north(); 422 | &[left], &[right] { 423 | @include bubb-north-left-and-right(); 424 | } 425 | &[left] { 426 | @include bubb-north-left(); 427 | } 428 | &[right] { 429 | @include bubb-north-right(); 430 | } 431 | } 432 | &[east] { 433 | @include bubb-east-all(); 434 | @include bubb-east(); 435 | &[left] { 436 | @include bubb-east-top(); 437 | } 438 | &[right] { 439 | @include bubb-east-bottom(); 440 | } 441 | } 442 | &[west] { 443 | @include bubb-west-all(); 444 | @include bubb-west(); 445 | &[left] { 446 | @include bubb-west-top(); 447 | } 448 | &[right] { 449 | @include bubb-west-bottom(); 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /scss/demo.scss: -------------------------------------------------------------------------------- 1 | $col: #444; 2 | $col2: #fad; 3 | $font-size: 1.5em; 4 | $max-width: 720px; 5 | 6 | @font-face { 7 | font-family: 'Walsheim'; 8 | font-weight: bold; 9 | font-style: bold; 10 | src: url('./fonts/gt-walsheim-bold-web.ttf'); 11 | } 12 | @font-face { 13 | font-family: 'Walsheim'; 14 | font-weight: normal; 15 | font-style: normal; 16 | src: url('./fonts/gt-walsheim-light-web.ttf'); 17 | } 18 | * { 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | 24 | @mixin hovers() { 25 | > div { 26 | display: inline-block; 27 | font-size: 95%; 28 | margin-top: 1em; 29 | padding-bottom: 2px; 30 | border-bottom: 2px dotted $col2; 31 | cursor: help; 32 | position: static; 33 | &:hover { 34 | border-color: transparent; 35 | } 36 | } 37 | > i { 38 | &:after { 39 | content: '⟵ :hover'; 40 | display: inline-block; 41 | white-space: nowrap; 42 | border-bottom: none; 43 | margin-left: 12px; 44 | font-size: 75%; 45 | opacity: .35; 46 | } 47 | &.click:after { 48 | content: '⟵ :click'; 49 | } 50 | } 51 | } 52 | 53 | html { 54 | overflow-y: scroll; 55 | } 56 | body { 57 | padding: 0; 58 | margin: 0; 59 | background: #fff; 60 | color: $col; 61 | font-family: 'Walsheim', arial, sans-serif; 62 | font-weight: normal; 63 | font-size: $font-size; 64 | line-height: 1; 65 | letter-spacing: .025rem; 66 | position: relative; 67 | min-height: 100vh; 68 | box-sizing: border-box; 69 | max-width: 100vw; 70 | overflow: hidden; 71 | } 72 | header { 73 | font-size: 1em; 74 | padding: 2.5em calc( (100vw - #{$max-width}) / 2 ); 75 | box-sizing: border-box; 76 | width: 100%; 77 | position: relative; 78 | display: block; 79 | background: $col; 80 | color: #fff; 81 | &:last-of-type { 82 | background: #FFDEDE; 83 | color: #444; 84 | a { 85 | color: #444; 86 | &:hover { 87 | color: #4797B1; 88 | } 89 | } 90 | } 91 | aside { 92 | margin-top: .75em; 93 | a { 94 | color: #F7F3CE; 95 | text-decoration: none; 96 | font-size: 50%; 97 | text-transform: uppercase; 98 | letter-spacing: 2px; 99 | margin: .5em .5em 0 0; 100 | &:hover { 101 | opacity: .75; 102 | } 103 | &:before { 104 | content:''; 105 | max-height: 0; 106 | overflow: hidden; 107 | display: block; 108 | } 109 | &.inline:before { 110 | content: ' | '; 111 | max-height: none; 112 | display: inline-block; 113 | opacity: .35; 114 | margin-right: .75em; 115 | font-size: 150%; 116 | position: relative; 117 | top: 4px; 118 | } 119 | &.first { 120 | display: block; 121 | padding-top: 1em; 122 | margin-left: 0; 123 | } 124 | } 125 | } 126 | i { 127 | font-style: normal; 128 | font-size: 50%; 129 | opacity: .65; 130 | position: relative; 131 | 132 | &:first-of-type { 133 | top: -.85em; 134 | } 135 | &:last-of-type { 136 | left: -1.1em; 137 | } 138 | margin-right: -.75em; 139 | } 140 | b { 141 | font-weight: bold; 142 | } 143 | span { 144 | float: right; 145 | } 146 | &:after { 147 | content:none; 148 | position:absolute; 149 | width:100vw; 150 | height:7.3em; 151 | display:block; 152 | background:rgba(0,0,0,.035); 153 | top:-3.25em; 154 | left:-7.5vw; 155 | } 156 | } 157 | code { 158 | font-size: 70%; 159 | //padding: 2em; 160 | overflow-x: scroll; 161 | &::-webkit-scrollbar { 162 | width: 0px; 163 | background: transparent; 164 | } 165 | } 166 | pre { 167 | //padding: .5em; 168 | font-size: 16px; 169 | //border: 1px solid #f0f0f0; 170 | opacity: .35; 171 | transition: opacity .25s linear; 172 | position: relative; 173 | z-index: 0; 174 | filter: grayscale(100%); 175 | &:hover { 176 | opacity: 1; 177 | filter: grayscale(0%); 178 | } 179 | } 180 | .opaque pre { 181 | opacity: 1; 182 | filter: grayscale(0%); 183 | padding: 0; 184 | border: none; 185 | font-size: 18px; 186 | } 187 | 188 | section { 189 | position: relative; 190 | max-width: $max-width; 191 | margin: 0 auto; 192 | padding: .5em 0 4em; 193 | box-sizing: border-box; 194 | display: block; 195 | width: 90%; 196 | &:first-of-type { 197 | padding: 0 0 4em; 198 | .header { 199 | font-weight: bold; 200 | width: 100%; 201 | height: 6em; 202 | line-height: 6em; 203 | text-decoration: none; 204 | background: #F7F3CE; 205 | display: block; 206 | padding: 0 5%; 207 | box-sizing: border-box; 208 | text-transform: uppercase; 209 | letter-spacing: 2px; 210 | font-size: 11px; 211 | color: #444; 212 | margin-bottom: 4em; 213 | white-space: nowrap; 214 | } 215 | } 216 | > p { 217 | font-size: 70%; 218 | line-height: 1.3; 219 | color: #888; 220 | } 221 | article { 222 | position: relative; 223 | > p { 224 | font-size: 70%; 225 | line-height: 1.3; 226 | color: #888; 227 | } 228 | > span { 229 | @include hovers(); 230 | } 231 | @include hovers(); 232 | } 233 | } 234 | footer { 235 | background: #F7F3CE; 236 | display: block; 237 | position: relative; 238 | height: 4.5em; 239 | width: 100%; 240 | line-height: 4.5em; 241 | a { 242 | position: absolute; 243 | top: 0; 244 | left: calc( (100vw - #{$max-width}) / 2 ); 245 | text-transform: uppercase; 246 | letter-spacing: 2px; 247 | font-size: 11px; 248 | text-decoration: none; 249 | font-weight: bold; 250 | color: #444; 251 | 252 | &:before { 253 | display: inline-block; 254 | font-weight: normal; 255 | margin-right: 6px; 256 | content: 'By'; 257 | } 258 | /* 259 | &:last-of-type { 260 | left: auto; 261 | right: calc( (100vw - #{$max-width}) / 2 ); 262 | &:before { 263 | content: 'Download Bubb on'; 264 | } 265 | } 266 | */ 267 | } 268 | } 269 | #eventsDisplay { 270 | background: rgba(#0ff1ce, .7); 271 | padding: 0 7.5vw; 272 | box-sizing: border-box; 273 | line-height: 100vh; 274 | height: 100vh; 275 | width: 100vw; 276 | font-size: 65%; 277 | font-style: normal; 278 | text-transform: uppercase; 279 | text-align: center; 280 | letter-spacing: 2px; 281 | position: fixed; 282 | top: 0; 283 | right: 0; 284 | pointer-events: none; 285 | z-index: 9999999; 286 | &[color="0"] { 287 | background: rgba(#ffdede, 0.6); 288 | } 289 | &[color="1"] { 290 | background: rgba(#F7F3CE, 0.6); 291 | } 292 | &[color="2"] { 293 | background: rgba(#C5ECBE, 0.6); 294 | } 295 | &[color="3"] { 296 | background: rgba(#4797B1, 0.6); 297 | } 298 | &[color="4"] { 299 | background: rgba(#54656b, 0.6); 300 | } 301 | &:empty { 302 | visibility: hidden; 303 | } 304 | } 305 | 306 | .wait { 307 | opacity: 0; 308 | transition: opacity .25s linear; 309 | @at-root { 310 | .done & { 311 | opacity: 1; 312 | } 313 | } 314 | } 315 | 316 | 317 | section ul { 318 | font-size: 70%; 319 | list-style: none; 320 | color: #888; 321 | padding-left: 50px; 322 | line-height: 1.2; 323 | padding-top: .75em; 324 | li { 325 | margin-bottom: inherit; 326 | position: relative; 327 | &:before { 328 | position: absolute; 329 | top: .6em; 330 | height: 1px; 331 | width: 35px; 332 | left: -50px; 333 | background: #888; 334 | display: block; 335 | content: ''; 336 | } 337 | u { 338 | text-decoration: none; 339 | display: block; 340 | font-weight: bold; 341 | padding-bottom: 1px; 342 | } 343 | } 344 | &:last-of-type li u { 345 | display: inline-block; 346 | margin-right: .5em; 347 | } 348 | } 349 | hr { 350 | margin: 2.5em 0 1em; 351 | opacity: .25; 352 | @at-root { 353 | section:last-of-type & { 354 | margin: 2em 0 1em; 355 | &:last-of-type { 356 | margin: 1.5em 0; 357 | } 358 | } 359 | } 360 | } 361 | 362 | .bubble-bobble { 363 | width: 44px; 364 | height: 44px; 365 | background: url(/assets/images/bubble_bobble.png); 366 | background-repeat: no-repeat; 367 | background-size: 100%; 368 | position: fixed; 369 | bottom: 1em; 370 | right: 1em; 371 | &:hover { 372 | background-position: 0 100%; 373 | } 374 | } 375 | 376 | ul.share-buttons { 377 | list-style: none; 378 | padding: 0; 379 | margin: .75em 0 0; 380 | &.device { 381 | background: #444; 382 | padding: 2em 0; 383 | text-align: center; 384 | } 385 | } 386 | 387 | ul.share-buttons li { 388 | display: inline; 389 | &:before { 390 | content: none; 391 | } 392 | &:hover img { 393 | opacity: .75; 394 | } 395 | img { 396 | min-width: 32px; 397 | min-height: 32px; 398 | max-width: 32px; 399 | margin: 0 4px; 400 | display: inline-block; 401 | } 402 | } 403 | 404 | ul.share-buttons .sr-only { 405 | position: absolute; 406 | clip: rect(1px 1px 1px 1px); 407 | clip: rect(1px, 1px, 1px, 1px); 408 | padding: 0; 409 | border: 0; 410 | height: 1px; 411 | width: 1px; 412 | overflow: hidden; 413 | } 414 | 415 | 416 | 417 | device { 418 | display: none; 419 | } 420 | 421 | 422 | 423 | @mixin noSelect() { 424 | -webkit-touch-callout: none; 425 | -webkit-user-select: none; 426 | -khtml-user-select: none; 427 | -moz-user-select: none; 428 | -ms-user-select: none; 429 | user-select: none; 430 | } 431 | 432 | #toggle { 433 | float: left; 434 | width: 50%; 435 | } 436 | #toggler { 437 | float: right; 438 | width: 50%; 439 | text-align: right; 440 | pre { 441 | visibility: hidden; 442 | } 443 | @include noSelect(); 444 | } 445 | #added { 446 | display: block; 447 | width: 100%; 448 | clear: both; 449 | } 450 | #all-directions { 451 | position: absolute; 452 | top: 100px; 453 | right: 0; 454 | width: 80px; 455 | height: 78px; 456 | border: none; 457 | background: transparent; 458 | border-radius: 50%; 459 | background-image: url(images/cirque.svg); 460 | background-repeat: no-repeat; 461 | > div { 462 | display: inline-block; 463 | z-index: 1; 464 | position: absolute; 465 | width: auto; 466 | height: auto; 467 | cursor: crosshair; 468 | &:after { 469 | content: ''; 470 | background: rgba(255, 255, 255, 0.8); 471 | min-width: 24px; 472 | min-height: 30px; 473 | position: absolute; 474 | transition: background .25s ease; 475 | } 476 | &:hover:after { 477 | background: rgba(255, 255, 255, 0); 478 | } 479 | &.n { 480 | &:nth-child(1) { 481 | top: 7.5%; 482 | left: 25%; 483 | &:after { 484 | transform: translate(-50%, -50%) rotate(-30deg); 485 | } 486 | } 487 | &:nth-child(2) { 488 | z-index: 9; 489 | top: 0; 490 | left: 50%; 491 | &:after { 492 | transform: translate(-50%, -50%); 493 | } 494 | } 495 | &:nth-child(3) { 496 | top: 7.5%; 497 | left: 75%; 498 | &:after { 499 | transform: translate(-50%, -50%) rotate(30deg); 500 | } 501 | } 502 | } 503 | &.e { 504 | &:nth-child(4) { 505 | right: 7.5%; 506 | top: 25%; 507 | &:after { 508 | transform: translate(-50%, -50%) rotate(60deg); 509 | } 510 | } 511 | &:nth-child(5) { 512 | right: 0; 513 | top: 50%; 514 | &:after { 515 | transform: translate(-50%, -50%) rotate(90deg); 516 | } 517 | } 518 | &:nth-child(6) { 519 | right: 7.5%; 520 | top: 75%; 521 | &:after { 522 | transform: translate(-50%, -50%) rotate(120deg); 523 | } 524 | } 525 | } 526 | &.s { 527 | &:nth-child(7) { 528 | bottom: 7.5%; 529 | left: 25%; 530 | &:after { 531 | transform: translate(-50%, -50%) rotate(30deg); 532 | } 533 | } 534 | &:nth-child(8) { 535 | z-index: 9; 536 | bottom: 0; 537 | left: 50%; 538 | &:after { 539 | transform: translate(-50%, -50%); 540 | } 541 | } 542 | &:nth-child(9) { 543 | bottom: 7.5%; 544 | left: 75%; 545 | &:after { 546 | transform: translate(-50%, -50%) rotate(-30deg); 547 | } 548 | } 549 | } 550 | &.w { 551 | &:nth-child(10) { 552 | left: 7.5%; 553 | top: 25%; 554 | &:after { 555 | transform: translate(-50%, -50%) rotate(120deg); 556 | } 557 | } 558 | &:nth-child(11) { 559 | left: 0; 560 | top: 50%; 561 | &:after { 562 | transform: translate(-50%, -50%) rotate(90deg); 563 | } 564 | } 565 | &:nth-child(12) { 566 | left: 7.5%; 567 | top: 75%; 568 | &:after { 569 | transform: translate(-50%, -50%) rotate(60deg); 570 | } 571 | } 572 | } 573 | } 574 | 575 | > span { 576 | position: absolute; 577 | width: 0; 578 | height: 0; 579 | background: pink; 580 | left: 50%; 581 | top: 50%; 582 | transform: rotate(0deg); 583 | transition: all .25s ease; 584 | opacity: 1; 585 | > i img { 586 | position: absolute; 587 | width: 35px; 588 | height: 35px; 589 | left: 50%; 590 | top: 50%; 591 | transform: translate(-50%, -50%); 592 | } 593 | /* 594 | > i { 595 | position: absolute; 596 | width:0; 597 | height: 0; 598 | border-left: 8px solid transparent; 599 | border-right: 8px solid transparent; 600 | border-bottom: 20px solid #444; 601 | border-top: none; 602 | left: 50%; 603 | top: 50%; 604 | transform: translate(-50%, -60%); 605 | } 606 | */ 607 | } 608 | &:hover > span { 609 | opacity: 1; 610 | } 611 | 612 | 613 | } 614 | 615 | 616 | 617 | 618 | /* 619 | --- bubb modifiers 620 | */ 621 | @mixin bubbMenuStyle() { 622 | padding: 0!important; 623 | div { 624 | padding: .75em 0; 625 | text-transform: uppercase; 626 | letter-spacing: 2px; 627 | font-size: 12px; 628 | font-weight: bold; 629 | transition: background .3s cubic-bezier(0,0,0,.75); 630 | cursor: pointer; 631 | &:first-child { 632 | padding-top: 1em; 633 | } 634 | &:hover { 635 | text-decoration: line-through; 636 | } 637 | } 638 | } 639 | 640 | .bubb { 641 | p { 642 | padding: 0; 643 | margin: 0; 644 | } 645 | b { 646 | font-weight: bold; 647 | letter-spacing: 1px; 648 | font-size: 90%; 649 | } 650 | @include noSelect(); 651 | } 652 | 653 | .bubb { 654 | img { 655 | max-width: 100%; 656 | display: block; 657 | margin: 0 auto .5em; 658 | } 659 | } 660 | .bubb-menu { 661 | bubb-bobb { 662 | @include bubbMenuStyle(); 663 | background: #FFDEDE!important; 664 | border-bottom-color: #FFDEDE!important; 665 | color: #444!important; 666 | div:nth-child(2) { 667 | background: #F7F3CE; 668 | color: #444; 669 | } 670 | div:nth-child(3) { 671 | background: #C5ECBE; 672 | color: #444; 673 | } 674 | div:nth-child(4) { 675 | background: #4797B1; 676 | color: #fff; 677 | } 678 | div:nth-child(5) { 679 | background: #54656b; 680 | border-radius: 0 0 4px 4px; 681 | color: #fff; 682 | } 683 | } 684 | } 685 | 686 | .tipcolor { 687 | border-bottom-color: #FFDEDE!important; 688 | } 689 | 690 | 691 | 692 | 693 | 694 | 695 | @media all and (max-width: 550px) { 696 | hide { 697 | display: none; 698 | } 699 | device { 700 | display: block; 701 | } 702 | body { 703 | font-size: 5vw; 704 | padding-bottom: 0; 705 | } 706 | header, footer { 707 | padding: 1.5em; 708 | } 709 | section { 710 | text-align: center; 711 | padding: 4em 1.5em; 712 | 713 | } 714 | section > span { 715 | display: block; 716 | } 717 | section i { 718 | display: none; 719 | } 720 | section:first-of-type .header { 721 | margin-bottom: 3em; 722 | position: relative; 723 | left: -5vw; 724 | width: 100vw; 725 | text-align: center; 726 | font-size: 50%; 727 | &.download { 728 | background: #C5ECBE; 729 | margin-bottom: 0; 730 | } 731 | &.info { 732 | background: #FFDEDE; 733 | margin-bottom: 0; 734 | } 735 | &.image { 736 | height: auto; 737 | padding: 0; 738 | margin: 0; 739 | display: block; 740 | } 741 | } 742 | section:last-of-type { 743 | padding: 1.35em 0; 744 | } 745 | section > p, section ul { 746 | font-size: 70%; 747 | } 748 | section ul { 749 | padding-left: 0; 750 | li:before { 751 | content: none; 752 | } 753 | } 754 | footer { 755 | position: static; 756 | text-align: center; 757 | padding: 2em 0 4em; 758 | line-height: 1.2; 759 | a { 760 | position: static; 761 | } 762 | } 763 | header { 764 | font-size: 1.25em; 765 | 766 | aside { 767 | a { 768 | line-height: 1.5; 769 | font-size: 35%; 770 | &.inline:before { 771 | content: ''; 772 | } 773 | &.first { 774 | display: inline-block; 775 | } 776 | } 777 | } 778 | } 779 | pre { 780 | display: none; 781 | } 782 | .opaque pre { 783 | display: block; 784 | text-align: left; 785 | opacity: 1; 786 | filter: none; 787 | font-size: 80%; 788 | padding: 1em 10% 0; 789 | } 790 | .bubble-bobble { 791 | width: 32px; 792 | height: 32px; 793 | background-size: 32px; 794 | &:hover { 795 | background-position: 0 -32px; 796 | } 797 | } 798 | #all-directions { 799 | display: none; 800 | } 801 | } 802 | --------------------------------------------------------------------------------