├── .gitignore ├── .github └── FUNDING.yml ├── secrets_default.json ├── .babelrc ├── src ├── ui │ ├── Element.js │ ├── icons │ │ ├── IconCheck.js │ │ └── IconShow.js │ ├── components │ │ ├── toolbar │ │ │ ├── ToolbarComponent.css │ │ │ └── ToolbarComponent.js │ │ ├── display │ │ │ ├── DisplayComponent.css │ │ │ └── DisplayComponent.js │ │ └── select │ │ │ ├── SelectComponent.css │ │ │ └── SelectComponent.js │ ├── views │ │ └── form │ │ │ ├── FormView.css │ │ │ └── FormView.js │ ├── Bulletproof.css │ └── FigmaUI.css ├── utils │ ├── Router.js │ ├── Tracking.js │ └── DisplayNetwork.js ├── AppState.js ├── App.js ├── App.css └── Core.js ├── manifest.json ├── .editorconfig ├── README.md ├── package.json ├── LICENSE └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules/ 4 | dist/ 5 | .nova 6 | secrets.json 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://paypal.me/basiclines', 'https://www.buymeacoffee.com/basiclines'] 2 | -------------------------------------------------------------------------------- /secrets_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "README": "DUPLICATE ME AS secrets.json", 3 | "AMPLITUDE_KEY": "" 4 | } 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-custom-element-classes", 5 | "webpack-alias" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/ui/Element.js: -------------------------------------------------------------------------------- 1 | import LEOElement from 'leo/element' 2 | 3 | class Element extends LEOElement { 4 | } 5 | 6 | export default Element 7 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Content Buddy", 3 | "id": "731260490045684148", 4 | "api": "1.0.0", 5 | "ui": "dist/ui.html", 6 | "main": "dist/core.js", 7 | "editorType": [ 8 | "figjam", 9 | "figma" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | 10 | [*.{js, html, css}] 11 | indent_style = tab 12 | indent_size = 2 13 | 14 | [*.{yml, conf, json}] 15 | indent_style = space 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Content Buddy 2 | *A Figma Plugin that makes replacing text content in multiple layers super easy for anyone.* 3 | 4 | Content Buddy searches for text content in your selection and displays a list with each unique text content, then you can select each content individually and set a replacement text for all the nodes where the original content appears. 5 | 6 | Content Buddy works in instances, components, frames and groups but it can't replace text in layers with multiple text styles (a plugin API limitation for now). 7 | -------------------------------------------------------------------------------- /src/ui/icons/IconCheck.js: -------------------------------------------------------------------------------- 1 | import Element from 'src/ui/Element' 2 | 3 | class IconCheck extends Element { 4 | render() { 5 | return` 6 | 7 | 8 | 9 | ` 10 | } 11 | } 12 | 13 | customElements.define('i-check', IconCheck) 14 | -------------------------------------------------------------------------------- /src/ui/icons/IconShow.js: -------------------------------------------------------------------------------- 1 | import Element from 'src/ui/Element' 2 | 3 | class IconShow extends Element { 4 | render() { 5 | return` 6 | 7 | 8 | 9 | 10 | ` 11 | } 12 | } 13 | 14 | customElements.define('i-show', IconShow) 15 | -------------------------------------------------------------------------------- /src/ui/components/toolbar/ToolbarComponent.css: -------------------------------------------------------------------------------- 1 | c-toolbar { 2 | position: relative; 3 | z-index: 5; 4 | display: block; 5 | padding: 0 16px 0 8px; 6 | overflow: hidden; 7 | border-bottom: solid 1px var(--color-separator); 8 | } 9 | c-toolbar nav, c-toolbar header { overflow: hidden; } 10 | 11 | c-toolbar nav ul { display: flex; flex-direction: row; float: left; } 12 | c-toolbar nav li a { 13 | display: block; 14 | padding: 12px 8px; 15 | font: var(--type-medium-bold); 16 | color: var(--color-text-tertiary); 17 | transition: 0.05s ease-in color; 18 | } 19 | c-toolbar nav .active a, c-toolbar nav li a:hover { color: var(--color-text-primary); text-decoration: none; } 20 | -------------------------------------------------------------------------------- /src/utils/Router.js: -------------------------------------------------------------------------------- 1 | import LEOObject from 'leo/object' 2 | 3 | let singleton = null 4 | class Router extends LEOObject { 5 | constructor() { 6 | super() 7 | this.url = window.location.hash 8 | this.history = [] 9 | this.routes = {} 10 | this.bind() 11 | 12 | if (!singleton) singleton = this 13 | return singleton 14 | } 15 | 16 | get root() { return '' } 17 | 18 | updateURLBar(url) { 19 | window.location.hash = url 20 | } 21 | 22 | appendToHistory(url) { 23 | this.history.push(url) 24 | } 25 | 26 | navigate(url) { 27 | this.appendToHistory(url) 28 | this.updateURLBar(url) 29 | this.url = url 30 | } 31 | 32 | bind() { 33 | window.addEventListener('hashchange', (e) => this.url = window.location.hash) 34 | } 35 | } 36 | 37 | export default new Router() 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "NODE_ENV=development npx webpack --mode=development --watch", 4 | "build": "NODE_ENV=production npx webpack --mode=production", 5 | "test": "echo \"Error: no test specified\" && exit 1" 6 | }, 7 | "devDependencies": { 8 | "babel-core": "^6.26.0", 9 | "babel-loader": "^7.1.2", 10 | "babel-plugin-transform-custom-element-classes": "^0.1.0", 11 | "babel-plugin-webpack-alias": "^2.1.2", 12 | "babel-preset-env": "^1.6.1", 13 | "css-loader": "^3.5.2", 14 | "html-webpack-inline-source-plugin": "0.0.10", 15 | "html-webpack-plugin": "^3.2.0", 16 | "style-loader": "^1.1.4", 17 | "url-loader": "^4.1.0", 18 | "webpack": "^4.42.1", 19 | "webpack-cli": "^3.3.11" 20 | }, 21 | "dependencies": { 22 | "@basiclines/leo": "^0.6.4", 23 | "ua-parser-js": "^0.7.33" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ismael González 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ui/components/display/DisplayComponent.css: -------------------------------------------------------------------------------- 1 | c-display[hidden] { display: block!important; height: 0; padding: 0 16px; opacity: 0.4; border: none; pointer-events: none; } 2 | 3 | c-display { display: block; padding: 8px 16px 16px; background: var(--color-decorator-soft); border-bottom: solid 1px var(--color-decorator-regular); transition: all 0.25s ease-out; } 4 | c-display picture { position: relative; width: 48px; height: 48px; border-radius: 8px; margin-right: 16px; float: left; overflow: hidden; } 5 | c-display picture img { width: 100%; } 6 | c-display picture:after { content: ""; position: absolute; left: 0; top: 0; right: 0; bottom: 0; border: solid 1px var(--color-decorator-regular); border-radius: 8px; } 7 | 8 | c-display section { overflow: hidden; } 9 | c-display em { display: block; font: var(--type-small-normal); color: var(--color-text-tertiary); margin: 0 0 8px; } 10 | c-display h1 { font: var(--type-large-bold); line-height: 16px; color: var(--color-text-primary); margin-bottom: 4px; } 11 | c-display p { font: var(--type-medium-normal); color: var(--color-text-secondary); margin-bottom: 8px; } 12 | c-display a { font: var(--type-medium-medium); color: var(--color-interactive-positive); } 13 | -------------------------------------------------------------------------------- /src/ui/components/toolbar/ToolbarComponent.js: -------------------------------------------------------------------------------- 1 | import './ToolbarComponent.css' 2 | import Router from 'src/utils/Router' 3 | import Tracking from 'src/utils/Tracking' 4 | import Element from 'src/ui/Element' 5 | 6 | class ToolbarComponent extends Element { 7 | 8 | beforeMount() { 9 | this.data.currentView = Router.url 10 | } 11 | 12 | onClick(e) { 13 | Tracking.track('clickToolbarLink', { url: event.target.getAttribute('href') }) 14 | } 15 | 16 | bind() { 17 | super.bind() 18 | Router.on('change:url', url => this.data.currentView = url) 19 | } 20 | 21 | render() { 22 | let currentView = this.data.currentView 23 | let isIndex = (currentView === Router.routes.index || currentView === '' || typeof currentView === 'undefined') 24 | let isPreferences = (currentView === Router.routes.preferences) 25 | 26 | return` 27 | 33 | ` 34 | } 35 | } 36 | 37 | customElements.define('c-toolbar', ToolbarComponent) 38 | -------------------------------------------------------------------------------- /src/AppState.js: -------------------------------------------------------------------------------- 1 | import Tracking from 'src/utils/Tracking' 2 | import LEOObject from 'leo/object' 3 | 4 | const defaultState = { 5 | appInit: false, 6 | replacementMode: 'default', 7 | selection: [], 8 | selectedPrompt: '', 9 | OpenAIToken: '' 10 | } 11 | 12 | let singleton = null 13 | class AppState extends LEOObject { 14 | constructor() { 15 | super(Object.assign({}, defaultState)) 16 | if (!singleton) singleton = this 17 | return singleton 18 | } 19 | 20 | setAppInit(UUID, OPENAI_TOKEN) { 21 | Tracking.setup(WP_AMPLITUDE_KEY, UUID) 22 | this.appInit = true 23 | this.OpenAIToken = OPENAI_TOKEN 24 | } 25 | 26 | setAppEmptyState() { 27 | this.trigger('empty') 28 | } 29 | 30 | setAppRenderState(data) { 31 | this.trigger('render', data) 32 | } 33 | 34 | setAppReplacedState(replacement, original) { 35 | this.trigger('replaced', { replacement: replacement, original: original || '' }) 36 | } 37 | 38 | addSelection(selection) { 39 | this.selection.push(selection) 40 | } 41 | 42 | clearSelection(selection) { 43 | this.selection = this.selection.filter(item => selection != item) 44 | } 45 | 46 | clearAllSelections() { 47 | this.selection = defaultState.selection 48 | } 49 | 50 | setReplacementMode(mode) { 51 | this.replacementMode = mode 52 | } 53 | 54 | setSelectedPrompt(prompt) { 55 | this.selectedPrompt = prompt 56 | } 57 | 58 | setOpenAIToken(token) { 59 | this.OpenAIToken = token 60 | parent.postMessage({ pluginMessage: { type: 'savePreference', options: { preference: "OPENAI_TOKEN", value: token } } }, '*') 61 | } 62 | 63 | } 64 | 65 | export default new AppState() 66 | -------------------------------------------------------------------------------- /src/ui/components/display/DisplayComponent.js: -------------------------------------------------------------------------------- 1 | import './DisplayComponent.css' 2 | import Element from 'src/ui/Element' 3 | import DisplayNetwork from 'src/utils/DisplayNetwork' 4 | import Tracking from 'src/utils/Tracking' 5 | 6 | const PLUGIN_NAME = 'content_buddy' 7 | 8 | class DisplayComponent extends Element { 9 | 10 | beforeMount() { 11 | // avoid display without proper data 12 | if (this.attrs.lastshowndate == 'undefined') return 13 | 14 | DisplayNetwork.getAvailableAd(this.attrs.lastshowndate, this.attrs.lastshownimpression) 15 | .then(ad => { 16 | // if we have an available ad, then render and display it 17 | if (!!ad) { 18 | ad.link = ad.link + '&utm_campaign='+PLUGIN_NAME 19 | this.data.ad = ad 20 | this.showDisplay() 21 | } 22 | }) 23 | } 24 | 25 | showDisplay() { 26 | this.removeAttribute('hidden') 27 | Tracking.track('displayImpression', { campaign: this.data.ad.tracking }) 28 | parent.postMessage({ pluginMessage: { type: 'displayImpression' } }, '*') 29 | } 30 | 31 | bind() { 32 | this.addEventListener('click', e => { 33 | if (e.target.getAttribute('data-trigger') == 'cta') { 34 | Tracking.track('displayClick', { campaign: this.data.ad.tracking }) 35 | } 36 | }) 37 | } 38 | 39 | render() { 40 | if (!this.data.ad) return 41 | 42 | let ad = this.data.ad 43 | return ` 44 | Sponsored 45 | 46 |
47 |

${ad.headline}

48 |

${ad.description}

49 | ${this.data.ad.cta} 50 |
51 | ` 52 | } 53 | } 54 | 55 | customElements.define('c-display', DisplayComponent) 56 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import 'src/ui/Bulletproof.css' 2 | import 'src/App.css' 3 | import 'src/ui/FigmaUI.css' 4 | 5 | 6 | import AppState from 'src/AppState' 7 | import Tracking from 'src/utils/Tracking' 8 | import 'src/ui/views/form/FormView' 9 | import 'src/ui/components/display/DisplayComponent' 10 | import Element from 'src/ui/Element' 11 | 12 | class ui extends Element { 13 | 14 | beforeMount() { 15 | window.addEventListener('message', (e) => { 16 | var msg = event.data.pluginMessage 17 | 18 | // init events 19 | if (msg.type === 'init') { 20 | AppState.setAppInit(msg.UUID, msg.OPENAI_TOKEN) 21 | Tracking.track('openPlugin', { selection: msg.selection }) 22 | this.insertDisplay(msg.AD_LAST_SHOWN_DATE, msg.AD_LAST_SHOWN_IMPRESSION) 23 | } else 24 | if (msg.type === 'init-empty') { 25 | AppState.setAppInit(msg.UUID) 26 | AppState.setAppEmptyState() 27 | Tracking.track('openPlugin', { selection: msg.selection }) 28 | } 29 | 30 | // state trigger events 31 | if (msg.type === 'render') { 32 | AppState.setAppRenderState(msg.uniques) 33 | } else 34 | if (msg.type === 'replaced') { 35 | AppState.setAppReplacedState(msg.replacement) 36 | } else 37 | if (msg.type === 'multipleReplaced') { 38 | AppState.setAppReplacedState(msg.replacement, msg.original) 39 | } 40 | }) 41 | } 42 | 43 | insertDisplay(lastShownDate, lastShownImpression) { 44 | let elem = document.createElement('c-display') 45 | elem.setAttribute('lastshowndate', lastShownDate) 46 | elem.setAttribute('lastshownimpression', lastShownImpression) 47 | elem.setAttribute('hidden', '') 48 | this.insertBefore(elem, this.find('v-form')) 49 | } 50 | 51 | removeDisplay() { 52 | this.find('c-display').setAttribute('hidden') 53 | } 54 | 55 | render() { 56 | return` 57 | 58 | ` 59 | } 60 | } 61 | 62 | customElements.define('root-ui', ui) 63 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const secrets = require('./secrets.json') 2 | const webpack = require('webpack') 3 | const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const path = require('path') 6 | 7 | module.exports = (env, argv) => ({ 8 | mode: argv.mode === 'production' ? 'production' : 'development', 9 | 10 | // This is necessary because Figma's 'eval' works differently than normal eval 11 | devtool: argv.mode === 'production' ? false : 'inline-source-map', 12 | 13 | entry: { 14 | ui: './src/App.js', // The entry point for your UI code 15 | core: './src/Core.js', // The entry point for your plugin code 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | // Enables including CSS by doing "import './file.css'" in your TypeScript code 21 | { test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] }, 22 | 23 | { test: /\.(png|jpg|gif|webp)$/, loader: [{ loader: 'url-loader' }] }, 24 | 25 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } 26 | ] 27 | }, 28 | 29 | // Webpack tries these extensions for you if you omit the extension like "import './file'" 30 | resolve: { 31 | extensions: ['.js'], 32 | alias: { 33 | src: path.resolve(__dirname, 'src/'), 34 | leo: path.resolve(__dirname, 'node_modules/@basiclines/leo/dist/'), 35 | } 36 | }, 37 | 38 | output: { 39 | filename: '[name].js', 40 | path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist" 41 | }, 42 | 43 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it 44 | plugins: [ 45 | new webpack.DefinePlugin({ 46 | 'WP_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 47 | 'WP_AMPLITUDE_KEY': JSON.stringify(secrets.AMPLITUDE_KEY) 48 | }), 49 | new HtmlWebpackPlugin({ 50 | templateContent: ``, 51 | filename: 'ui.html', 52 | inlineSource: '.(js)$', 53 | chunks: ['ui'], 54 | }), 55 | new HtmlWebpackInlineSourcePlugin() 56 | ] 57 | }) 58 | -------------------------------------------------------------------------------- /src/ui/views/form/FormView.css: -------------------------------------------------------------------------------- 1 | /* PLUGIN UI */ 2 | * { border: 0; padding: 0; margin: 0; } 3 | .hidden { display: none!important; } 4 | form { padding: 16px; } 5 | fieldset { margin-bottom: 16px; } 6 | p.type { margin-bottom: 16px; } 7 | p.type--11-pos-bold { margin-bottom: 8px; } 8 | 9 | .search { display: block; position: relative; } 10 | .search .icon { position: absolute; left: 0; top: -1px; } 11 | .search .input { text-indent: 24px; } 12 | .list { height: 256px; overflow: auto; margin: 16px -16px 0; padding-bottom: 16px; border-bottom: solid 1px #D5D5D5; } 13 | .item { display: flex; flex-direction: row; align-items: center; justify-content: flex-start; cursor: pointer; padding: 0 16px; } 14 | .item * { pointer-events: none; } 15 | .item .icon { flex-shrink: 0; opacity: 0; margin-right: 0px; } 16 | .item p { margin-bottom: 0; padding: 4px 0; } 17 | 18 | .item:hover { background: rgba(72, 159, 244, 0.2); } 19 | .item.empty p { color: #888; font-style: italic; } 20 | .item.selected p { color: #2C84DB; } 21 | .item.selected .icon { opacity: 1; } 22 | 23 | 24 | .main-actions p { margin-bottom: 0; padding-left: 8px; } 25 | .main-actions p + p { margin: 8px 0; } 26 | .main-actions p i { color: var(--color-text-secondary); font-style: normal; } 27 | .main-actions fieldset { border-top: solid 1px var(--color-decorator-regular); padding-top: 8px; margin-top: 8px; } 28 | .main-actions textarea { min-height: 100px; resize: vertical; margin-top: 8px; } 29 | 30 | .main-actions button { margin-top: 8px; position: relative; min-width: 92px; } 31 | .main-actions button.loading { text-indent: -999px; } 32 | .main-actions button.loading::after { 33 | content: ""; 34 | display: block; 35 | position: absolute; 36 | left: 50%; 37 | top: 50%; 38 | width: 10px; 39 | height: 10px; 40 | border-radius: 50%; 41 | border: 2px solid white; 42 | border-top-color: transparent; 43 | animation: spin 0.8s linear infinite; 44 | margin: -7px 0 0 -7px; 45 | } 46 | 47 | /* Define the animation for the loading state */ 48 | @keyframes spin { 49 | from { 50 | transform: rotate(0deg); 51 | } 52 | to { 53 | transform: rotate(360deg); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/Tracking.js: -------------------------------------------------------------------------------- 1 | import UAParser from 'ua-parser-js' 2 | 3 | const UUIDKey = 'UUID' 4 | let singleton = null 5 | 6 | class Tracking { 7 | constructor() { 8 | this.root = 'https://api.amplitude.com/httpapi' 9 | this.apiKey = '' 10 | this.userId = '' 11 | this.userProps = {} 12 | this.UUID = '' 13 | this.UA = '' 14 | this.parser = new UAParser() 15 | 16 | if (!singleton) singleton = this 17 | return singleton 18 | } 19 | 20 | createUUID(a) { 21 | // See: https://github.com/amplitude/Amplitude-Javascript/blob/master/src/uuid.js 22 | var uuid = function(a) { 23 | return a // if the placeholder was passed, return 24 | ? ( // a random number from 0 to 15 25 | a ^ // unless b is 8, 26 | Math.random() // in which case 27 | * 16 // a random number from 28 | >> a / 4 // 8 to 11 29 | ).toString(16) // in hexadecimal 30 | : ( // or otherwise a concatenated string: 31 | [1e7] + // 10000000 + 32 | -1e3 + // -1000 + 33 | -4e3 + // -4000 + 34 | -8e3 + // -80000000 + 35 | -1e11 // -100000000000, 36 | ).replace( // replacing 37 | /[018]/g, // zeroes, ones, and eights with 38 | uuid // random hex digits 39 | ); 40 | } 41 | return uuid() 42 | } 43 | 44 | setup(apiKey, UUID) { 45 | this.apiKey = apiKey 46 | this.UUID = UUID 47 | this.UA = this.parser.getResult() 48 | } 49 | 50 | track(event, props) { 51 | if (WP_ENV == 'development') return console.log(event, props || {}) 52 | 53 | let evtObj = { 54 | user_id: this.userId, 55 | device_id: this.UUID, 56 | event_type: event, 57 | os_name: this.UA.os.name, 58 | os_version: this.UA.os.version, 59 | platform: `${this.UA.browser.name} ${this.UA.browser.major}`, 60 | language: navigator.language, 61 | user_properties: this.userProps, 62 | event_properties: props, 63 | time: Math.floor(Date.now()) 64 | } 65 | 66 | var data = new FormData() 67 | data.append( "api_key", this.apiKey) 68 | data.append( "event", JSON.stringify(evtObj)) 69 | 70 | fetch(this.root, { 71 | method: 'POST', 72 | body: data 73 | }) 74 | } 75 | 76 | } 77 | 78 | export default new Tracking() 79 | -------------------------------------------------------------------------------- /src/ui/components/select/SelectComponent.css: -------------------------------------------------------------------------------- 1 | c-select { 2 | position: relative; 3 | display: block; 4 | -webkit-box-sizing: border-box; 5 | box-sizing: border-box; 6 | width: 100%; 7 | cursor: default; 8 | } 9 | 10 | .select-menu[disabled] { 11 | opacity: 0.3; 12 | } 13 | 14 | c-select button { 15 | font: var(--type-small-normal); 16 | position: relative; 17 | display: flex; 18 | justify-content: flex-start; 19 | align-items: center; 20 | width: 100%; 21 | height: 30px; 22 | margin: 1px 0 1px 0; 23 | padding: 6px 0 6px 8px; 24 | cursor: default; 25 | color: rgba(0, 0, 0, 0.8); 26 | border-radius: 2px; 27 | } 28 | 29 | c-select button:hover { box-shadow: 0 0 0 1px var(--color-separator) inset; } 30 | c-select button:focus, c-select button:active { 31 | box-shadow: 0 0 0 2px var(--color-interactive-positive) inset; 32 | outline: none; 33 | } 34 | c-select button .c-icon { pointer-events: none; } 35 | c-select button .c-icon.default path { fill: var(--color-text-tertiary); } 36 | c-select button .c-icon.hover { opacity: 0; position: absolute; right: 4px; top: 7px; } 37 | c-select button .c-icon.hover path { fill: var(--color-text-primary); } 38 | c-select:hover button .c-icon.default { opacity: 0; } 39 | c-select:hover button .c-icon.hover { opacity: 1; } 40 | c-select button span { pointer-events: none; } 41 | 42 | c-select[reverse] button { flex-direction: row-reverse; padding: 6px 8px 6px 0; } 43 | c-select[reverse] button .c-icon.default { margin: 0 4px 0 0; } 44 | c-select[reverse] button .c-icon.hover { right: auto; left: 4px; } 45 | 46 | c-select ul { 47 | position: absolute; 48 | z-index: 2; 49 | display: flex; 50 | flex-direction: column; 51 | width: 100%; 52 | margin: 0; 53 | padding: 8px 0 8px 0; 54 | border-radius: 2px; 55 | background-color: var(--color-text-primary); 56 | box-shadow: 0 5px 17px rgba(0, 0, 0, 0.2), 0 2px 7px rgba(0, 0, 0, 0.15); 57 | } 58 | 59 | c-select li { 60 | font: var(--type-medium-normal); 61 | display: flex; 62 | align-items: center; 63 | height: 24px; 64 | padding: 0 8px; 65 | color: #FFFFFF; 66 | } 67 | c-select li:hover { background-color: var(--color-interactive-positive); } 68 | 69 | c-select li.separator { pointer-events: none; height: 1px; margin: 8px 0; background-color: rgba(255, 255, 255, 0.2); } 70 | c-select li .c-icon { display: inline-block; opacity: 0; margin-right: 8px; pointer-events: none; } 71 | c-select li .c-icon path { fill: #FFFFFF; stroke: #FFFFFF; } 72 | c-select li[selected] .c-icon { opacity: 1; } 73 | -------------------------------------------------------------------------------- /src/utils/DisplayNetwork.js: -------------------------------------------------------------------------------- 1 | let singleton = null 2 | const DAILY_INTERVAL = 86400000 3 | const WEEKLY_INTERVAL = DAILY_INTERVAL*7 4 | const MONTHLY_INTERVAL = WEEKLY_INTERVAL*4 5 | 6 | class DisplayNetwork { 7 | 8 | constructor() { 9 | this.root = 'https://figma-plugins-display-network.netlify.app/api.json' 10 | this.timeStampNow = Date.now() 11 | 12 | if (!singleton) singleton = this 13 | return singleton 14 | } 15 | 16 | getAds() { 17 | return fetch(this.root, { method: 'GET' }) 18 | } 19 | 20 | getAvailableAd(lastShownDate, lastShownImpression) { 21 | return new Promise((resolve, reject) => { 22 | this.getAds().then(response => { 23 | response.json().then(ads => { 24 | 25 | let availableAds = ads.reduce((prev, current) => { 26 | if (this.checkImpressionAvailability(parseInt(lastShownDate), parseInt(lastShownImpression), current)) { 27 | prev.push(current) 28 | } 29 | return prev 30 | }, []) 31 | 32 | resolve(availableAds[this.getRandomInt(0, availableAds.length)]) 33 | }) 34 | }).catch(error => console.log('Error getAvailableAd', error)) 35 | }) 36 | } 37 | 38 | getRandomInt(min, max) { 39 | min = Math.ceil(min); 40 | max = Math.floor(max); 41 | return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive 42 | } 43 | 44 | getAdInterval(ad) { 45 | if (WP_ENV == 'development') return 10000 46 | 47 | return parseInt(ad.impression_net_interval) 48 | } 49 | 50 | checkImpressionAvailability(lastShownDate, lastShownImpression, ad) { 51 | 52 | let adInterval = this.getAdInterval(ad) 53 | let availableTimeWindow = (!lastShownDate || lastShownDate + adInterval < this.timeStampNow) 54 | 55 | // Check if the ad is optimised for impression rather than time 56 | if (ad.impression_count) { 57 | let availableImpression = (lastShownImpression < parseInt(ad.impression_count)) 58 | 59 | if (availableTimeWindow && availableImpression || !availableTimeWindow && availableImpression) { 60 | // meanwhile there is impressions left, spend the impression 61 | return true 62 | } else 63 | if (availableTimeWindow && !availableImpression) { 64 | // when impressions limit is reached and time window is valid, reset the impression 65 | parent.postMessage({ pluginMessage: { type: 'resetImpression' } }, '*') 66 | return false 67 | } else { 68 | return false 69 | } 70 | } else { 71 | return availableTimeWindow 72 | } 73 | 74 | } 75 | } 76 | 77 | export default new DisplayNetwork() 78 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family:'Inter'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url("https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.7") format("woff") 6 | } 7 | 8 | @font-face { 9 | font-family:'Inter'; 10 | font-style: normal; 11 | font-weight: 500; 12 | src: url("https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7") format("woff") 13 | } 14 | 15 | @font-face { 16 | font-family: 'Inter'; 17 | font-style: normal; 18 | font-weight: 600; 19 | src: url("https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7") format("woff") 20 | } 21 | 22 | :root { 23 | --type-small-normal: 400 11px/16px "Inter", sans-serif; 24 | --type-small-medium: 500 11px/16px "Inter", sans-serif; 25 | --type-small-bold: 600 11px/16px "Inter", sans-serif; 26 | --type-medium-normal: 400 12px/16px "Inter", sans-serif; 27 | --type-medium-medium: 500 12px/16px "Inter", sans-serif; 28 | --type-medium-bold: 600 12px/16px "Inter", sans-serif; 29 | --type-large-normal: 400 13px/24px "Inter", sans-serif; 30 | --type-large-medium: 500 13px/24px "Inter", sans-serif; 31 | --type-large-bold: 600 13px/24px "Inter", sans-serif; 32 | --type-xlarge-normal: 400 14px/24px "Inter", sans-serif; 33 | --type-xlarge-medium: 500 14px/24px "Inter", sans-serif; 34 | --type-xlarge-bold: 600 14px/24px "Inter", sans-serif; 35 | 36 | --color-interactive-positive: #18A0FB; 37 | --color-interactive-negative: #E41761; 38 | --color-text-primary: #03121C; 39 | --color-text-secondary: #64646C; 40 | --color-text-tertiary: #AAAAB2; 41 | --color-background-primary: #FFFFFF; 42 | --color-decorator-strong: #C5CED2; 43 | --color-decorator-regular: #D5DDE2; 44 | --color-decorator-soft: #F1F4F4; 45 | 46 | --color-separator: rgba(0, 0, 0, 0.12); 47 | } 48 | 49 | root-ui { transition: opacity 0.05s linear; display: block; text-align: left; } 50 | root-ui[notready] { opacity: 0; pointer-events: none; } 51 | 52 | .view { 53 | display: block; 54 | position: absolute; 55 | top: 40px; 56 | left: 0; 57 | right: 0; 58 | bottom: 0; 59 | overflow-y: auto; 60 | padding: 16px; 61 | } 62 | 63 | .icon { 64 | display: inline-flex; 65 | align-items: center; 66 | justify-content: center; 67 | width: 16px; 68 | height: 16px; 69 | } 70 | .icon svg { pointer-events: none; } 71 | 72 | /* Icon Actions */ 73 | button.icon, a.icon { 74 | width: 32px; 75 | height: 32px; 76 | font: 0/0 a; 77 | border-radius: 2px; 78 | cursor: pointer; 79 | } 80 | button.icon:hover, a.icon:hover { background-color: var(--color-decorator-soft); } 81 | button.icon:active, a.icon:active { box-shadow: 0 0 0 2px var(--color-interactive-positive) inset; } 82 | button.icon *, a.icon * { pointer-events: none; } 83 | -------------------------------------------------------------------------------- /src/ui/components/select/SelectComponent.js: -------------------------------------------------------------------------------- 1 | import './SelectComponent.css' 2 | import Element from 'src/ui/Element' 3 | import LEOObject from 'leo/object' 4 | import 'src/ui/icons/IconShow' 5 | import 'src/ui/icons/IconCheck' 6 | 7 | class SelectComponent extends Element { 8 | 9 | toggleList() { 10 | let list = this.find('[data-select=list]'); 11 | (list.hasAttribute('hidden')) ? list.removeAttribute('hidden') : list.setAttribute('hidden', '') 12 | } 13 | 14 | selectItem(item) { 15 | let value = '' 16 | let group = item.getAttribute('data-group') 17 | let isToggle = (item.hasAttribute('data-toggle')) 18 | 19 | if (!isToggle) { 20 | let list = this.findAll(`[data-select=item][data-group=${group}]`) 21 | value = item.getAttribute('data-value') 22 | list.forEach(node => node.removeAttribute('selected')) 23 | item.setAttribute('selected', '') 24 | if (!this.attrs.label) this.find('[data-select=label]').innerHTML = item.getAttribute('data-label') 25 | } else { 26 | let values = JSON.parse(item.getAttribute('data-toggle')) 27 | let isSelected = (item.hasAttribute('selected')); 28 | value = (isSelected) ? `${values[0]}` : `${values[1]}`; 29 | (isSelected) ? item.removeAttribute('selected') : item.setAttribute('selected', ''); 30 | } 31 | this.toggleList() 32 | this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: value, group: group } })) 33 | } 34 | 35 | onClick(e) { 36 | if (e.target.getAttribute('data-trigger') == 'open') { 37 | this.toggleList() 38 | } 39 | 40 | if (e.target.getAttribute('data-select') == 'item') { 41 | this.selectItem(e.target) 42 | } 43 | } 44 | 45 | renderItem(item) { 46 | let isSelected = (item.hasAttribute('selected')) 47 | let hasGroup = (item.hasAttribute('group')) 48 | let hasToggle = (item.hasAttribute('toggle')) 49 | let toggleValues = (item.getAttribute('toggle')) 50 | let groupName = item.getAttribute('group') 51 | let valueAttr = (hasToggle) ? `data-toggle="${toggleValues}"` : `data-value="${item.value}"`; 52 | let putSeparator = (hasGroup && this.lastGroup != '' && this.lastGroup != groupName) 53 | if (hasGroup) this.lastGroup = groupName 54 | 55 | return ` 56 | ${(putSeparator) ? `
  • ` : ''} 57 |
  • 58 | 59 | ${item.innerText} 60 |
  • 61 | ` 62 | } 63 | 64 | render() { 65 | this.lastGroup = '' 66 | let defaults = Array.from(this.findAll('option')) 67 | let defaultSelection = defaults.reduce((buffer, item) => { 68 | if (item.hasAttribute('selected')) buffer += item.innerText 69 | return buffer 70 | }, '') 71 | 72 | return` 73 | 78 | 84 | ` 85 | } 86 | } 87 | 88 | customElements.define('c-select', SelectComponent) 89 | -------------------------------------------------------------------------------- /src/ui/Bulletproof.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Bulletproof 3 | * All base styles that we need to normalize the starting point for cross-browser development. 4 | */ 5 | 6 | 7 | /*Base font */ 8 | html { min-height: 100%; position: relative; overflow-x: hidden; } 9 | body { margin: 0; padding: 0; text-align: center; vertical-align: middle; overflow-x: hidden; } 10 | 11 | 12 | /* Text conventions */ 13 | h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; font-weight: normal; } 14 | ul, ol, dl, dt, dd { padding: 0; margin: 0; list-style-type: none; } 15 | p, li { 16 | word-wrap: break-word; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | q { quotes: none; display: block; } 21 | pre, blockquote { padding: 0; margin: 0; } 22 | em { font-style: italic; } 23 | address { font-style: normal; display: inline; } 24 | abbr, acronym { cursor: help; border: none; } 25 | 26 | 27 | /* Headings */ 28 | h2 { font-weight: bold; } 29 | 30 | 31 | /* Links */ 32 | a { text-decoration: none; outline: none; } 33 | a:hover { text-decoration: underline; } 34 | a img { border: none; } 35 | a abbr { cursor: pointer; } 36 | 37 | 38 | /* Tables */ 39 | table { border-collapse: collapse; border-spacing: 0; width: 100%; } 40 | td { vertical-align: top; } 41 | caption, th { text-align: left; } 42 | 43 | 44 | /* Forms */ 45 | form { margin: 0; } 46 | fieldset { margin: 0; padding: 0; border: none; } 47 | legend { padding: 0; display: block; } 48 | 49 | button { height: auto; width: auto; overflow: visible; display: inline-block; vertical-align: middle; background: none; border: none; outline: none; white-space: nowrap; cursor: pointer; } 50 | button::-moz-focus-inner { padding: 0; border: none; } 51 | 52 | input, textarea, select, button { margin: 0; padding: 0; vertical-align: middle; } 53 | input:focus, textarea:focus { outline: none!important; } 54 | textarea { resize: none; overflow: auto; } 55 | 56 | input[type="text"], 57 | input[type="email"], 58 | input[type="number"], 59 | textarea { } 60 | 61 | input[type="text"]:focus, 62 | input[type="email"]:focus, 63 | input[type="number"]:focus, 64 | textarea:focus { } 65 | 66 | input:invalid { box-shadow: none; } 67 | input:-moz-submit-invalid { box-shadow: none; } 68 | input:-moz-ui-invalid { box-shadow:none; } 69 | 70 | input[type=number]::-webkit-outer-spin-button, 71 | input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } 72 | input[type=number] { -moz-appearance:textfield; } 73 | 74 | [contenteditable] { outline: none; word-wrap: break-word; } 75 | textarea::-webkit-input-placeholder, 76 | input::-webkit-input-placeholder { color: inherit; opacity: 1; } 77 | 78 | textarea::-moz-placeholder, 79 | input::-moz-placeholder { color: inherit; opacity: 1; } 80 | 81 | textarea:focus::-webkit-input-placeholder, 82 | input:focus::-webkit-input-placeholder { color: inherit; } 83 | 84 | textareafir:focus::-moz-placeholder, 85 | input:focus::-moz-placeholder { color: inherit; } 86 | 87 | input:-webkit-autofill { 88 | -webkit-box-shadow: 0 0 0 50px white inset; 89 | -webkit-text-fill-color: inherit; 90 | } 91 | input:-webkit-autofill:focus { 92 | -webkit-box-shadow: 0 0 0 50px white inset; 93 | -webkit-text-fill-color: inherit; 94 | } 95 | 96 | 97 | /* Media */ 98 | img { display: block; padding: 0; margin: 0; } 99 | img:-moz-broken { line-height: inherit; overflow: hidden; } 100 | 101 | 102 | /* HTML 5 */ 103 | article, aside, details, figcaption, figure, footer, header, hgroup, 104 | menu, nav, section, video, audio, canvas, progress, meter, time 105 | { display: block; padding: 0; margin: 0; } 106 | 107 | [hidden] { display: none!important; } 108 | 109 | /* Clearfix */ 110 | .cf { *zoom: 1; } .cf:before,.cf:after { content: ""; display: table; } .cf:after { clear: both; } 111 | -------------------------------------------------------------------------------- /src/Core.js: -------------------------------------------------------------------------------- 1 | import Tracking from 'src/utils/Tracking' 2 | 3 | var orderedUniques = [] 4 | var previewNodes = [] 5 | var initialSelection = figma.currentPage.selection.slice(0) 6 | var initMessage = {} 7 | 8 | // Obtain UUID then trigger init event 9 | Promise.all([ 10 | figma.clientStorage.getAsync('UUID'), 11 | figma.clientStorage.getAsync('OPENAI_TOKEN'), 12 | figma.clientStorage.getAsync('AD_LAST_SHOWN_DATE'), 13 | figma.clientStorage.getAsync('AD_LAST_SHOWN_IMPRESSION') 14 | ]).then(promisesData => { 15 | 16 | let UUID = promisesData[0] 17 | let OPENAI_TOKEN = promisesData[1] || '' 18 | let AD_LAST_SHOWN_DATE = promisesData[2] || 572083200 // initial date, if no date was saved previously 19 | let AD_LAST_SHOWN_IMPRESSION = promisesData[3] || 0 // initial impressions 20 | 21 | if (!UUID) { 22 | UUID = Tracking.createUUID() 23 | figma.clientStorage.setAsync('UUID', UUID) 24 | } 25 | 26 | initMessage = { 27 | type: 'init', 28 | UUID: UUID, 29 | OPENAI_TOKEN: OPENAI_TOKEN, 30 | AD_LAST_SHOWN_DATE: AD_LAST_SHOWN_DATE, 31 | AD_LAST_SHOWN_IMPRESSION: AD_LAST_SHOWN_IMPRESSION, 32 | selection: initialSelection.length 33 | } 34 | 35 | function getTextNodesFrom(selection) { 36 | var nodes = [] 37 | function childrenIterator(node) { 38 | if (node.children) { 39 | node.children.forEach(child => { 40 | childrenIterator(child) 41 | }) 42 | } else { 43 | if (node.type === 'TEXT') nodes.push({ id: node.id, characters: node.characters }) 44 | if (node.type === 'SHAPE_WITH_TEXT') nodes.push({ id: node.id, characters: node.text.characters }) 45 | } 46 | } 47 | 48 | selection.forEach(item => childrenIterator(item)) 49 | return nodes 50 | } 51 | 52 | function renderContent(selection) { 53 | var selection = selection 54 | var textNodes = getTextNodesFrom(selection) 55 | var uniques = {} 56 | 57 | textNodes.map(item => { 58 | if (typeof uniques[item.characters] != 'undefined') { 59 | uniques[item.characters].push(item.id) 60 | } else { 61 | uniques[item.characters] = [item.id] 62 | } 63 | }) 64 | 65 | for (var item in uniques) { 66 | orderedUniques.push({ key: item, nodes: uniques[item] }) 67 | } 68 | 69 | // Sort alphabetically 70 | orderedUniques.sort(function(a, b) { 71 | var nameA = a.key.toUpperCase() 72 | var nameB = b.key.toUpperCase() 73 | if (nameA < nameB) { 74 | return -1; 75 | } 76 | if (nameA > nameB) { 77 | return 1; 78 | } 79 | return 0; 80 | }) 81 | 82 | // Sort by length 83 | orderedUniques.sort(function(a, b) { 84 | if (a.key.length < b.key.length) { 85 | return -1; 86 | } 87 | if (a.key.length > b.key.length) { 88 | return 1; 89 | } 90 | return 0; 91 | }) 92 | 93 | var message = { type: 'render', uniques: orderedUniques } 94 | figma.ui.postMessage(message) 95 | } 96 | 97 | if (figma.currentPage.selection.length === 0){ 98 | figma.showUI(__html__, { width: 320, height: 80 }) 99 | initMessage.type = 'init-empty' 100 | figma.ui.postMessage(initMessage) 101 | } else { 102 | figma.showUI(__html__, { width: 320, height: 544 }) 103 | figma.ui.postMessage(initMessage) 104 | renderContent(initialSelection) 105 | 106 | figma.ui.onmessage = msg => { 107 | 108 | if (msg.type === 'displayImpression') { 109 | figma.ui.resize(320, 544+124) 110 | figma.clientStorage.setAsync('AD_LAST_SHOWN_DATE', Date.now()) 111 | figma.clientStorage.setAsync('AD_LAST_SHOWN_IMPRESSION', parseInt(AD_LAST_SHOWN_IMPRESSION)+1) 112 | } 113 | 114 | if (msg.type === 'resetImpression') { 115 | figma.clientStorage.setAsync('AD_LAST_SHOWN_IMPRESSION', 0) 116 | } 117 | 118 | if (msg.type === 'previewNodes') { 119 | var idx = msg.options 120 | var nodes = orderedUniques[idx].nodes 121 | previewNodes = nodes.reduce((buffer, item) => { 122 | buffer.push(figma.getNodeById(item)) 123 | return buffer 124 | }, []) 125 | figma.currentPage.selection = previewNodes 126 | } 127 | 128 | if (msg.type === 'previewMultipleNodes') { 129 | var idx = msg.options 130 | var length = msg.length 131 | var nodes = orderedUniques[idx].nodes 132 | previewNodes = nodes.reduce((buffer, item) => { 133 | buffer.push(figma.getNodeById(item)) 134 | return buffer 135 | }, []) 136 | if (length == 1) { 137 | figma.currentPage.selection = previewNodes 138 | } else { 139 | figma.currentPage.selection = figma.currentPage.selection.concat(previewNodes) 140 | } 141 | } 142 | 143 | if (msg.type === 'restoreSelection') { 144 | let filteredSelection = figma.currentPage.selection.filter(node => { 145 | var wrapperNode = null 146 | 147 | // Check node types supported in Figma and FigJam files 148 | if (node.type === 'TEXT') { 149 | wrapperNode = node 150 | } else 151 | if (node.type === 'SHAPE_WITH_TEXT') { 152 | wrapperNode = node.text 153 | } 154 | 155 | 156 | return (wrapperNode.characters != msg.content) 157 | }) 158 | 159 | figma.currentPage.selection = (filteredSelection.length == 0) ? initialSelection : filteredSelection; 160 | } 161 | 162 | if (msg.type === 'freeMatch') { 163 | 164 | } 165 | 166 | if (msg.type === 'notify') { 167 | figma.notify(msg.content) 168 | } 169 | 170 | if (msg.type === 'savePreference') { 171 | figma.clientStorage.setAsync(msg.options.preference, msg.options.value) 172 | } 173 | 174 | if (msg.type === 'uniqueMatch') { 175 | var replacement = msg.options 176 | var alertOnce = false 177 | previewNodes.forEach(node => { 178 | var font = null 179 | var wrapperNode = null 180 | 181 | // Check node types supported in Figma and FigJam files 182 | if (node.type === 'TEXT') { 183 | wrapperNode = node 184 | } else 185 | if (node.type === 'SHAPE_WITH_TEXT') { 186 | wrapperNode = node.text 187 | } 188 | 189 | if (typeof wrapperNode.fontName != 'symbol') { 190 | font = wrapperNode.fontName 191 | figma.loadFontAsync(font).then(() => { 192 | wrapperNode.characters = replacement 193 | figma.ui.postMessage({ type: 'replaced', replacement: replacement }) 194 | figma.notify(`Replaced ${previewNodes.length} layers`) 195 | }) 196 | } else 197 | if (!alertOnce) { 198 | alertOnce = true 199 | alert('Content Buddy cannot modify text layers with mixed font properties.') 200 | } 201 | }) 202 | } 203 | 204 | if (msg.type === 'multipleMatch') { 205 | var original = msg.original 206 | var replacement = msg.result 207 | var alertOnce = false 208 | figma.currentPage.selection.forEach(node => { 209 | 210 | var font = null 211 | var wrapperNode = null 212 | 213 | // Check node types supported in Figma and FigJam files 214 | if (node.type === 'TEXT') { 215 | wrapperNode = node 216 | } else 217 | if (node.type === 'SHAPE_WITH_TEXT') { 218 | wrapperNode = node.text 219 | } 220 | 221 | if (typeof wrapperNode.fontName != 'symbol') { 222 | font = wrapperNode.fontName 223 | figma.loadFontAsync(font).then(() => { 224 | if (wrapperNode.characters == original) { 225 | wrapperNode.characters = replacement 226 | figma.ui.postMessage({ type: 'multipleReplaced', replacement: replacement, original: original }) 227 | figma.notify(`Replaced ${previewNodes.length} layers`) 228 | } 229 | }) 230 | } else 231 | if (!alertOnce) { 232 | alertOnce = true 233 | alert('Content Buddy cannot modify text layers with mixed font properties.') 234 | } 235 | }) 236 | } 237 | } 238 | } 239 | }) 240 | -------------------------------------------------------------------------------- /src/ui/views/form/FormView.js: -------------------------------------------------------------------------------- 1 | import './FormView.css' 2 | import Element from 'leo/element' 3 | import SelectComponent from 'src/ui/components/select/SelectComponent' 4 | import Tracking from 'src/utils/Tracking' 5 | import AppState from 'src/AppState' 6 | 7 | class FormView extends Element { 8 | 9 | get replaceMode() { 10 | return this.querySelector('#replace_mode') 11 | } 12 | 13 | get AIReplaceModeNode() { 14 | return this.querySelector('#ai_replace') 15 | } 16 | 17 | get selectAndEditPromptNode() { 18 | return this.querySelector('#select_and_edit_prompt') 19 | } 20 | 21 | get addOpenAITokenNode() { 22 | return this.querySelector('#add_openai_token') 23 | } 24 | 25 | get OpenAITokenNode() { 26 | return this.querySelector('#openai_token') 27 | } 28 | 29 | get selectPromptNode() { 30 | return this.querySelector('#ai_replace_prompt') 31 | } 32 | 33 | get promptDetailNode() { 34 | return this.querySelector('#prompt_detail') 35 | } 36 | 37 | get defaultReplaceModeNode() { 38 | return this.querySelector('#default_replace') 39 | } 40 | 41 | mount() { 42 | this.handleReplaceMode('simple') 43 | this.handleAddTokenView('') 44 | AppState.setSelectedPrompt(this.getPrompt('typos')) 45 | } 46 | 47 | getPrompt(id) { 48 | let prompts = { 49 | typos: 'Fix typos on the following text, fixed text should use the same language: ', 50 | translate: 'Translate to EN: ', 51 | shorter: 'Make a slightly shorter version of the following text, use the same language as provided: ', 52 | longer: 'Make a 10% longer version of the following text, longer text should use the same language: ', 53 | iterate: 'Create an alternative iteration of the following text keeping the same text length and use the same language: ' 54 | } 55 | return prompts[id] 56 | } 57 | 58 | bind() { 59 | // PLUGIN UI CONTROLS 60 | var empty = document.getElementById('empty') 61 | var content = document.getElementById('content') 62 | var check = document.getElementById('free_match_check') 63 | var free_match = document.getElementById('free_match') 64 | var unique_match = document.getElementById('unique_match') 65 | var query = document.getElementById('query') 66 | var uniques = document.getElementById('uniques') 67 | var match = document.getElementById('match') 68 | var replace = document.getElementById('replace') 69 | var applyDefault = this.defaultReplaceModeNode.querySelector('button') 70 | var applyAI = this.AIReplaceModeNode.querySelector('[data-trigger="applyReplaceAI"]') 71 | 72 | // Enable/disable free match 73 | check.addEventListener('change', e => { 74 | var enabled = check.checked 75 | if (enabled) { 76 | unique_match.classList.add('hidden') 77 | free_match.classList.remove('hidden') 78 | } else { 79 | unique_match.classList.remove('hidden') 80 | free_match.classList.add('hidden') 81 | } 82 | }) 83 | 84 | // Search box 85 | query.addEventListener('keydown', e => { 86 | setTimeout(() => { 87 | var value = query.value.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 88 | if (value != '') { 89 | uniques.querySelectorAll('.item').forEach(elem => { 90 | elem.classList.add('hidden') 91 | if (elem.getAttribute('key').search(value) != -1) elem.classList.remove('hidden') 92 | }) 93 | } else { 94 | uniques.querySelectorAll('.item').forEach(elem => { elem.classList.remove('hidden') }) 95 | } 96 | }, 25) 97 | }) 98 | 99 | query.addEventListener('click', e => { 100 | Tracking.track('focusSearch') 101 | }) 102 | 103 | // Click handlers for unique items 104 | uniques.addEventListener('click', e => { 105 | if (e.target.nodeName == 'LI') { 106 | var content = e.target.querySelector('p').textContent 107 | var idx = parseInt(e.target.getAttribute('idx')) 108 | 109 | if (e.target.classList.contains('selected')) { 110 | e.target.classList.remove('selected') 111 | applyDefault.setAttribute('disabled', '') 112 | applyAI.setAttribute('disabled', '') 113 | replace.value = '' 114 | AppState.clearSelection(content) 115 | parent.postMessage({ pluginMessage: { type: 'restoreSelection', options: idx, content: content } }, '*') 116 | Tracking.track('unselectContent') 117 | } else { 118 | AppState.addSelection(content) 119 | parent.postMessage({ pluginMessage: { type: 'previewMultipleNodes', options: idx, length: AppState.selection.length } }, '*') 120 | applyAI.removeAttribute('disabled') 121 | applyDefault.removeAttribute('disabled') 122 | Tracking.track('selectContent') 123 | e.target.classList.add('selected') 124 | } 125 | } 126 | }) 127 | 128 | // Apply Default replacements 129 | applyDefault.addEventListener('click', e => { 130 | var free_match = check.checked 131 | if (free_match) { 132 | parent.postMessage({ pluginMessage: { type: 'freeMatch', options: { match: match.value, replace: replace.value } } }, '*') 133 | } else { 134 | if (replace.value !== '' || replace.value === '' && confirm('Replace with empty content?')) { 135 | AppState.selection.forEach(content => { 136 | parent.postMessage({ pluginMessage: { type: 'multipleMatch', original: content, result: replace.value } }, '*') 137 | }) 138 | Tracking.track('clickReplace', { mode: 'default' }) 139 | } 140 | } 141 | }) 142 | 143 | // Apply AI replacements 144 | applyAI.addEventListener('click', e => { 145 | var free_match = check.checked 146 | if (free_match) { 147 | parent.postMessage({ pluginMessage: { type: 'freeMatch', options: { match: match.value, replace: replace.value } } }, '*') 148 | } else { 149 | applyAI.classList.add('loading') 150 | 151 | let ctx = this 152 | let selectionSnapshot = AppState.selection.slice() 153 | function sequentialIteration(iteration, limit) { 154 | 155 | // Finish iterations 156 | if (iteration > limit - 1) { 157 | applyAI.classList.remove('loading') 158 | return 159 | } 160 | 161 | let content = selectionSnapshot[iteration] 162 | console.log('content', content) 163 | ctx.requestAIResponse(ctx.promptDetailNode.value, content).then(response => { 164 | parent.postMessage({ pluginMessage: { type: 'multipleMatch', original: content, result: response } }, '*') 165 | let counter = ++iteration 166 | sequentialIteration(counter, limit) 167 | }).catch(err => { 168 | parent.postMessage({ pluginMessage: { type: 'notify', content: 'Error fetching AI response' } }, '*') 169 | let counter = ++iteration 170 | sequentialIteration(counter, limit) 171 | console.log('Error fetching AI response: ', err) 172 | }) 173 | } 174 | 175 | sequentialIteration(0, AppState.selection.length) 176 | Tracking.track('clickReplace', { mode: 'ai' }) 177 | } 178 | }) 179 | 180 | this.replaceMode.addEventListener('change', e => { 181 | let mode = e.detail.value 182 | AppState.setReplacementMode(mode) 183 | }) 184 | 185 | this.selectPromptNode.addEventListener('change', e => { 186 | let promptId = e.detail.value 187 | AppState.setSelectedPrompt(this.getPrompt(promptId)) 188 | }) 189 | 190 | this.addOpenAITokenNode.querySelector('button').addEventListener('click', e => { 191 | let token = this.OpenAITokenNode.value 192 | AppState.setOpenAIToken(token) 193 | }) 194 | 195 | AppState.on('empty', () => { this.displayEmptyState() }) 196 | AppState.on('render', data => { this.renderUniques(data) }) 197 | AppState.on('replaced', (state) => { this.replaceUnique(state) }) 198 | AppState.on('change:replacementMode', mode => { this.handleReplaceMode(mode) }) 199 | AppState.on('change:OpenAIToken', token => { this.handleAddTokenView(token) }) 200 | AppState.on('change:selectedPrompt', prompt => { this.printPrompt(prompt) }) 201 | } 202 | 203 | requestAIResponse(prompt, text) { 204 | let payload = { 205 | model: "text-davinci-003", 206 | prompt: prompt+text, 207 | max_tokens: 2500, 208 | temperature: 0 209 | } 210 | 211 | return new Promise((resolve, reject) => { 212 | fetch("https://api.openai.com/v1/completions", { 213 | method: "POST", 214 | headers: { 215 | "Content-Type": "application/json", 216 | Authorization: `Bearer ${AppState.OpenAIToken}` 217 | }, 218 | body: JSON.stringify(payload) 219 | }) 220 | .then(response => response.json()) 221 | .then(data => resolve(data.choices[0].text.trim())) 222 | .catch(err => reject(err)) 223 | }) 224 | } 225 | 226 | handleAddTokenView(token) { 227 | if (token == '') { 228 | this.addOpenAITokenNode.removeAttribute('hidden') 229 | this.selectAndEditPromptNode.setAttribute('hidden', '') 230 | } else { 231 | this.selectAndEditPromptNode.removeAttribute('hidden') 232 | this.addOpenAITokenNode.setAttribute('hidden', '') 233 | } 234 | } 235 | 236 | handleReplaceMode(mode) { 237 | if (mode == 'simple') { 238 | this.defaultReplaceModeNode.removeAttribute('hidden') 239 | this.AIReplaceModeNode.setAttribute('hidden', '') 240 | } else if (mode == 'ai') { 241 | this.AIReplaceModeNode.removeAttribute('hidden') 242 | this.defaultReplaceModeNode.setAttribute('hidden', '') 243 | } 244 | } 245 | 246 | printPrompt(prompt) { 247 | this.promptDetailNode.value = prompt 248 | Tracking.track('selectPrompt', { prompt: prompt }) 249 | } 250 | 251 | displayEmptyState() { 252 | var empty = document.getElementById('empty') 253 | var content = document.getElementById('content') 254 | content.classList.add('hidden') 255 | empty.classList.remove('hidden') 256 | } 257 | 258 | renderUniqueItem(item, idx) { 259 | var emptyClass = (item.key == ' ') ? 'empty': ''; 260 | var text = (item.key == ' ') ? 'Empty layer': item.key; 261 | return ` 262 |
  • 263 |
    264 |

    ${text}

    265 |
  • 266 | ` 267 | } 268 | 269 | renderUniques(data) { 270 | let uniques = document.getElementById('uniques') 271 | uniques.innerHTML = data.reduce((buffer, item, idx) => buffer += this.renderUniqueItem(item, idx),'') 272 | } 273 | 274 | replaceUnique(state) { 275 | let replacement = state.replacement 276 | let original = state.original 277 | let uniques = document.getElementById('uniques') 278 | let nodes = uniques.querySelectorAll('.selected') 279 | 280 | nodes.forEach(node => { 281 | let contentNode = node.querySelector('[data-render=text]') 282 | if (contentNode.innerHTML == original || original == '') { 283 | node.setAttribute('key', replacement) 284 | contentNode.innerHTML = replacement 285 | AppState.clearSelection(original) 286 | AppState.addSelection(replacement) 287 | } 288 | }) 289 | } 290 | 291 | render() { 292 | return ` 293 |
    294 | 302 | 303 |
    304 | 313 | 314 |
    315 |

    1. Select your content

    316 | 320 |
      321 |
      322 | 323 | 327 | 328 |
      329 |

      2. Replacement mode

      330 | 331 | 332 | 333 | 334 | 335 |
      336 |

      Replace to

      337 | 338 | 339 |
      340 | 341 | 362 | 363 |
      364 |
      365 |
      366 | ` 367 | } 368 | } 369 | 370 | customElements.define('v-form', FormView) 371 | -------------------------------------------------------------------------------- /src/ui/FigmaUI.css: -------------------------------------------------------------------------------- 1 | /* FIGMA UI */ 2 | @font-face{font-family:Inter;font-style:normal;font-weight:400;src:url(https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.7) format("woff")}@font-face{font-family:Inter;font-style:normal;font-weight:500;src:url(https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7) format("woff")}@font-face{font-family:Inter;font-style:normal;font-weight:600;src:url(https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7) format("woff")}.icon{width:32px;height:32px;cursor:default;color:#000;background-repeat:no-repeat;background-position:0 0}.icon--blue{color:#18a0fb;background-position:0 -64px}.icon--black-3{color:rgba(0,0,0,.3);background-position:0 -32px}.icon--button{border:2px solid transparent;border-radius:2px;outline:0;background-position:-2px -2px}.icon--button:hover{background-color:rgba(0,0,0,.06)}.icon--button:active{border:2px solid #18a0fb;background-color:rgba(0,0,0,.06)}.icon--button:disabled{opacity:.37}.icon--text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-family:Inter,sans-serif;font-size:11px}.icon--adjust{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m12%2016.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m12%2048.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m12%2080.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--angle{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m12%2012v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m12%2044v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m12%2076v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--break{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13.0002%209v3h1v-3zm9.1031.89644c-1.1617-1.16176-3.0453-1.16176-4.2071.00002l-2.7499%202.74994.7071.7071%202.7499-2.7499c.7712-.77128%202.0217-.77129%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.75c1.1618-1.1617%201.1618-3.0453%200-4.20706zm-12.20691%2012.20706c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%20opacity%3D%22.9%22%2F%3E%3Cpath%20d%3D%22m13.0002%2041v3h1v-3zm9.1031.8964c-1.1617-1.1617-3.0453-1.1617-4.2071.0001l-2.7499%202.7499.7071.7071%202.7499-2.7499c.7712-.7713%202.0217-.7713%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.75c1.1618-1.1617%201.1618-3.0453%200-4.2071zm-12.20691%2012.2071c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%20opacity%3D%22.9%22%2F%3E%3Cpath%20d%3D%22m13.0002%2073v3h1v-3zm9.1031.8965c-1.1617-1.1618-3.0453-1.1618-4.2071%200l-2.7499%202.7499.7071.7071%202.7499-2.7499c.7712-.7713%202.0217-.7713%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.7499c1.1618-1.1618%201.1618-3.0454%200-4.2071zm-12.20691%2012.207c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%2318a0fb%22%20opacity%3D%22.9%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--close{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2015.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2047.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2079.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--ellipses{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m11.5%2016c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m11.5%2048c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m11.5%2080c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--eyedropper{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m22.4473%209.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m22.4473%2041.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m22.4473%2073.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fsvg%3E\a")}.icon--hidden{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m21.5085%2015.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m21.5085%2047.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m21.5085%2079.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--hyperlink{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13.5%2018c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m13.5%2050c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m13.5%2082c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--link-broken{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m18%2014v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m18%2046v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m18%2078v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--link{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2010c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2042c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2074c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--lock{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m17.5%2013.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m17.5%2045.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m17.5%2077.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--minus{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m21.5%2016.5h-11v-1h11z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m21.5%2048.5h-11v-1h11z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m21.5%2080.5h-11v-1h11z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--play{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13%2010.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m13%2042.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m13%2074.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--plus{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m15.5%2015.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m15.5%2047.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m15.5%2079.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--recent{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m23%2016c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m23%2048c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m23%2080c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--recent{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2023.9999c4.4183%200%208-3.5817%208-8s-3.5817-8.00002-8-8.00002-8%203.58172-8%208.00002%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2055.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2087.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--resolve-filled{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2023.9999c4.4183%200%208-3.5817%208-8s-3.5817-8.00002-8-8.00002-8%203.58172-8%208.00002%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2055.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2087.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--resolve{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m23%2015.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7.00002%207-7.00002%207%203.13402%207%207.00002zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8.00002%208-8.00002%208%203.58172%208%208.00002zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m23%2047.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m23%2079.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--search{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m20%2015c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m20%2047c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m20%2079c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--trash{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m15%209.5c-.5523%200-1%20.44772-1%201h4c0-.55228-.4477-1-1-1zm4%201c0-1.10457-.8954-2-2-2h-2c-1.1046%200-2%20.89543-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m15%2041.5c-.5523%200-1%20.4477-1%201h4c0-.5523-.4477-1-1-1zm4%201c0-1.1046-.8954-2-2-2h-2c-1.1046%200-2%20.8954-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m15%2073.5c-.5523%200-1%20.4477-1%201h4c0-.5523-.4477-1-1-1zm4%201c0-1.1046-.8954-2-2-2h-2c-1.1046%200-2%20.8954-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--unlock{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m18%2014v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m18%2046v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m18%2078v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--visible{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16.0001%2019c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16.0001%2051c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16.0001%2083c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:32px;padding:8px 4px 8px 8px;color:rgba(0,0,0,.3);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.section-title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:32px;padding:8px 4px 8px 8px;color:rgba(0,0,0,.8);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.005em}.type{margin:0;padding:0}.type--11-pos{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.type--11-pos-medium{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.type--11-pos-bold{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.005em}.type--12-pos{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:0}.type--12-pos-medium{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:12px;letter-spacing:0}.type--12-pos-bold{font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:12px;letter-spacing:0}.type--11-neg{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.01em}.type--11-neg-medium{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.01em}.type--11-neg-bold{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.01em}.type--12-neg{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:.005em}.type--12-neg-medium{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:12px;letter-spacing:.005em}.type--12-neg-bold{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:12px;letter-spacing:.005em}.button{display:inline-block;-ms-flex-negative:0;flex-shrink:0;margin:1px 0 1px 0;padding:5px 16px 5px 16px;border:2px solid transparent;border-radius:6px;outline:0}.button--primary{color:#fff;background-color:#18a0fb;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.01em}.button--primary:active,.button--primary:focus{border:2px solid rgba(0,0,0,.3)}.button--primary:disabled{background-color:rgba(0,0,0,.3)}.button--primary-destructive{color:#fff;background-color:#f24822}.button--primary-destructive:active,.button--primary-destructive:focus{border:2px solid rgba(0,0,0,.3)}.button--primary-destructive:disabled{opacity:.4}.button--secondary{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.8);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.button--secondary:active,.button--secondary:focus{padding:4px 23px 4px 23px;border:2px solid #18a0fb}.button--secondary:disabled{color:rgba(0,0,0,.3);border:1px solid rgba(0,0,0,.3)}.button--secondary-destructive{color:#f24822;border:1px solid #f24822;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.button--secondary-destructive:active,.button--secondary-destructive:focus{padding:4px 23px 4px 23px;border:2px solid #f24822}.button--secondary-destructive:disabled{opacity:.4}.input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0;padding:8px 4px 8px 7px;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.input:hover{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1)}.input:active,.input:focus{padding:8px 4px 8px 6px;color:#000;border:2px solid #18a0fb;border-radius:2px}.input::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.input::selection{color:#000;background-color:rgba(24,145,251,.3)}.input::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.input:-ms-input-placeholder{color:rgba(0,0,0,.3)}.input::-ms-input-placeholder{color:rgba(0,0,0,.3)}.input::placeholder{color:rgba(0,0,0,.3)}.input:disabled{color:rgba(0,0,0,.3)}.input-icon{position:relative;width:100%}.input-icon__icon{position:absolute;top:-1px;left:0;width:32px;height:32px}.input-icon__input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0;padding:8px 4px 8px 0;text-indent:32px;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.input-icon__input:hover{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1)}.input-icon__input:active,.input-icon__input:focus{margin-left:-1px;padding:8px 4px 8px 0;color:#000;border:2px solid #18a0fb;border-radius:2px}.input-icon__input::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.input-icon__input::selection{color:#000;background-color:rgba(24,145,251,.3)}.input-icon__input::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input:-ms-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input::-ms-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input::placeholder{color:rgba(0,0,0,.3)}.input-icon__input:disabled{color:rgba(0,0,0,.3)}.textarea{display:-webkit-box;display:-ms-flexbox;display:flex;overflow:hidden;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:62px;margin:1px 8px 1px 8px;padding:7px 4px 7px 7px;resize:none;color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1);border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.textarea:active,.textarea:focus{padding:6px 4px 6px 6px;color:#000;border:2px solid #18a0fb;border-radius:2px}.textarea::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.textarea::selection{color:#000;background-color:rgba(24,145,251,.3)}.textarea::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.textarea:-ms-input-placeholder{color:rgba(0,0,0,.3)}.textarea::-ms-input-placeholder{color:rgba(0,0,0,.3)}.textarea::placeholder{color:rgba(0,0,0,.3)}.textarea:disabled{color:rgba(0,0,0,.3)}.textarea:disabled:focus{border:1px solid rgba(0,0,0,.1)}.select-dropdown{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:100%;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.select-dropdown:last-child{margin-right:0}.select-dropdown__button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0!important;padding:0 8px 0 8px;text-align:left;cursor:pointer;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.select-dropdown__button span:after{display:inline-block;width:7px;height:5px;margin-top:6px;margin-left:6px;content:'';background-color:transparent;background-image:url(data:image/svg+xml;utf8,%3Csvg%20fill%3D%22none%22%20height%3D%225%22%20viewBox%3D%220%200%207%205%22%20width%3D%227%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m3%203.70711-3-3.000003.707107-.707107%202.646443%202.64645%202.64645-2.64645.70711.707107-3%203.000003-.35356.35355z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E)}.select-dropdown__button:hover{padding:0 8px 0 8px;border:1px solid rgba(0,0,0,.1)}.select-dropdown__button:hover .chevron-down{opacity:1}.select-dropdown__button:hover span:after{opacity:0}.select-dropdown__button .chevron-down{position:absolute;top:1px;right:0;width:30px;height:30px;opacity:0;background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2230%22%20viewBox%3D%220%200%2030%2030%22%20width%3D%2230%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m15%2016.7071-3-3%20.7071-.7071%202.6465%202.6464%202.6464-2.6464.7071.7071-3%203-.3535.3536z%22%20fill%3D%22%23000%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E\a");background-repeat:no-repeat;background-position:0 0}.select-dropdown__button--active,.select-dropdown__button:focus{width:100%;padding:0 7px 0 7px;border:2px solid #18a0fb;outline:0}.select-dropdown__button--active .chevron-down,.select-dropdown__button:focus .chevron-down{opacity:1}.select-dropdown__button--active span:after,.select-dropdown__button:focus span:after{opacity:0}.select-dropdown__list{position:absolute;z-index:2;top:31px;right:0;left:0;display:block;overflow:auto;width:100%;margin:0;padding:0;list-style-type:none;pointer-events:none;opacity:0;-webkit-box-shadow:0 5px 17px rgba(0,0,0,.2),0 2px 7px rgba(0,0,0,.15);box-shadow:0 5px 17px rgba(0,0,0,.2),0 2px 7px rgba(0,0,0,.15)}.select-dropdown__list:before{display:block;height:8px;content:'';border-top-left-radius:2px;border-top-right-radius:2px;background-color:#222}.select-dropdown__list:after{display:block;height:8px;content:'';border-bottom-right-radius:2px;border-bottom-left-radius:2px;background-color:#222}.select-dropdown__list.active{pointer-events:auto;opacity:1}.select-dropdown__list-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:24px;padding:0 16px 0 32px;list-style-type:none;text-align:left;cursor:pointer;color:#fff;background-color:#222;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:.005em}.select-dropdown__list-item:hover{color:#fff;background-color:#18a0fb}.select-dropdown__list-item--selected{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m13.2069%205.20724-5.50002%205.49996-.70711.7072-.70711-.7072-3-2.99996%201.41422-1.41421%202.29289%202.29289%204.79293-4.79289z%22%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E\a");background-repeat:no-repeat;background-position:8px 4px}.select-dropdown__list-item--initial{background-color:#18a0fb}.select-dropdown__divider{margin:0;padding:8px 0 8px 0;background-color:#222}.select-dropdown__line{display:block;height:1px;background-color:rgba(255,255,255,.2)}.switch{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-item-align:1;align-self:1;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;cursor:default}.switch__container{position:relative;width:24px;height:12px;margin:10px 16px 10px 8px}.switch__label{font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.switch__checkbox{width:0;height:0;opacity:0}.switch__checkbox:checked+.switch__slider{background-color:#000}.switch__checkbox:focus+.switch__slider{-webkit-box-shadow:0 0 1px #2196f3;box-shadow:0 0 1px #2196f3}.switch__checkbox:checked+.switch__slider:before{-webkit-transform:translateX(12px);transform:translateX(12px)}.switch__slider{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transition:background-color 0 .2s;transition:background-color 0 .2s;border:1px solid #000;border-radius:12px;background-color:#fff}.switch__slider::before{position:absolute;top:-1px;left:-1px;width:10px;height:10px;content:'';-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transition:background-color 0 .2s;transition:background-color 0 .2s;border:1px solid #000;border-radius:50%;background-color:#fff}.checkbox{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;height:28px;cursor:default}.checkbox__container{position:relative;width:32px;height:32px}.checkbox__label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-top:4px;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.checkbox__box{position:absolute;width:0;height:0;opacity:0}.checkbox__box:checked~.checkbox__mark{border:1px solid #18a0fb;background-color:#18a0fb}.checkbox__box:checked~.checkbox__mark:after{display:block}.checkbox__mark{position:absolute;top:10px;left:10px;width:12px;height:12px;border:1px solid #000;border-radius:2px;background-color:#fff}.checkbox__mark:after{position:absolute;width:12px;height:12px;content:'';background-image:url(data:image/svg+xml;utf8,%3Csvg%20fill%3D%22none%22%20height%3D%227%22%20viewBox%3D%220%200%208%207%22%20width%3D%228%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m1.17647%201.88236%201.88235%201.88236%203.76471-3.76472%201.17647%201.17648-4.94118%204.9412-3.05882-3.05884z%22%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E);background-repeat:no-repeat;background-position:1px 2px}.divider{display:block;width:100%;height:1px;margin:8px 0 8px 0;padding:0;background-color:#e5e5e5} 3 | --------------------------------------------------------------------------------