├── .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 |
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 |
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 |
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 |
79 | ${defaults.reduce((buffer, item) => {
80 | buffer += this.renderItem(item)
81 | return buffer
82 | }, '')}
83 |
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 |
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 |
--------------------------------------------------------------------------------