From 4bb1c98e0f28bcf1d0dff2d90d01013cd5487522 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 2 Jan 2020 20:36:10 +0200
Subject: [PATCH] Replaced `v3compat` with `source` to reduce code complexity.
 Made more slots customizable. `theme` now contains a snapshot of theme
 generated for better compatiblity and future-proofing

---
 .../style_switcher/style_switcher.js          |  68 +++---
 .../style_switcher/style_switcher.vue         |  14 +-
 src/services/style_setter/style_setter.js     |  42 ++--
 static/themes/breezy-dark.json                | 224 +++++++++++++++++-
 static/themes/breezy-light.json               | 220 ++++++++++++++++-
 5 files changed, 488 insertions(+), 80 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 9fe1bf59..f751260a 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,7 +1,15 @@
 import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js'
 import { set, delete as del } from 'vue'
 import { merge } from 'lodash'
-import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
+import {
+  generateColors,
+  generateShadows,
+  generateRadii,
+  generateFonts,
+  composePreset,
+  getThemes,
+  CURRENT_VERSION
+} from '../../services/style_setter/style_setter.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
@@ -135,15 +143,6 @@ export default {
     selectedVersion () {
       return Array.isArray(this.selected) ? 1 : 2
     },
-    currentCompat () {
-      return generateCompat({
-        shadows: this.shadowsLocal,
-        fonts: this.fontsLocal,
-        opacity: this.currentOpacity,
-        colors: this.currentColors,
-        radii: this.currentRadii
-      })
-    },
     currentColors () {
       return {
         bg: this.bgColorLocal,
@@ -339,27 +338,32 @@ export default {
         !this.keepColor
       )
 
-      const theme = {}
+      const source = {
+        themeEngineVersion: CURRENT_VERSION
+      }
 
       if (this.keepFonts || saveEverything) {
-        theme.fonts = this.fontsLocal
+        source.fonts = this.fontsLocal
       }
       if (this.keepShadows || saveEverything) {
-        theme.shadows = this.shadowsLocal
+        source.shadows = this.shadowsLocal
       }
       if (this.keepOpacity || saveEverything) {
-        theme.opacity = this.currentOpacity
+        source.opacity = this.currentOpacity
       }
       if (this.keepColor || saveEverything) {
-        theme.colors = this.currentColors
+        source.colors = this.currentColors
       }
       if (this.keepRoundness || saveEverything) {
-        theme.radii = this.currentRadii
+        source.radii = this.currentRadii
       }
 
+      const theme = this.previewTheme
+
+      console.log(source)
       return {
-        // To separate from other random JSON files and possible future theme formats
-        _pleroma_theme_version: 2, theme: merge(theme, this.currentCompat)
+        // To separate from other random JSON files and possible future source formats
+        _pleroma_theme_version: 2, theme, source
       }
     }
   },
@@ -392,7 +396,7 @@ export default {
       if (parsed._pleroma_theme_version === 1) {
         this.normalizeLocalState(parsed, 1)
       } else if (parsed._pleroma_theme_version >= 2) {
-        this.normalizeLocalState(parsed.theme, 2)
+        this.normalizeLocalState(parsed.theme, 2, parsed.source)
       }
     },
     importValidator (parsed) {
@@ -402,7 +406,7 @@ export default {
     clearAll () {
       const state = this.$store.getters.mergedConfig.customTheme
       const version = state.colors ? 2 : 'l1'
-      this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version)
+      this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version, this.$store.getters.mergedConfig.customThemeSource)
     },
 
     // Clears all the extra stuff when loading V1 theme
@@ -441,24 +445,30 @@ export default {
 
     /**
      * This applies stored theme data onto form. Supports three versions of data:
-     * v3 (version = 3) - same as 2 but with some incompatible changes
+     * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity
      * v2 (version = 2) - newer version of themes.
      * v1 (version = 1) - older version of themes (import from file)
      * v1l (version = l1) - older version of theme (load from local storage)
      * v1 and v1l differ because of way themes were stored/exported.
-     * @param {Object} input - input data
+     * @param {Object} theme - theme data (snapshot)
      * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
+     * @param {Object} source - theme source - this will be used if compatible
+     * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
+     *                           this allows importing source anyway
      */
-    normalizeLocalState (originalInput, version = 0) {
+    normalizeLocalState (theme, version = 0, source, forceSource = false) {
       let input
-      if (typeof originalInput.v3compat !== 'undefined') {
-        version = 3
-        input = merge(originalInput, originalInput.v3compat)
+      if (typeof source !== 'undefined') {
+        if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
+          input = source
+          version = source.themeEngineVersion
+        } else {
+          input = theme
+        }
       } else {
-        input = originalInput
+        input = theme
       }
 
-      const compat = input.v3compat
       const radii = input.radii || input
       const opacity = input.opacity
       const shadows = input.shadows || {}
@@ -615,7 +625,7 @@ export default {
           this.cOrangeColorLocal = this.selected[8]
         }
       } else if (this.selectedVersion >= 2) {
-        this.normalizeLocalState(this.selected.theme, 2)
+        this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
       }
     }
   }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 38ca2017..2eadbe25 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -106,7 +106,7 @@
             <OpacityInput
               v-model="bgOpacityLocal"
               name="bgOpacity"
-              :fallback="previewTheme.opacity.bg || 1"
+              :fallback="previewTheme.opacity.bg"
             />
             <ColorInput
               v-model="textColorLocal"
@@ -238,7 +238,7 @@
             <OpacityInput
               v-model="panelOpacityLocal"
               name="panelOpacity"
-              :fallback="previewTheme.opacity.panel || 1"
+              :fallback="previewTheme.opacity.panel"
             />
             <ColorInput
               v-model="panelTextColorLocal"
@@ -295,7 +295,7 @@
             <OpacityInput
               v-model="inputOpacityLocal"
               name="inputOpacity"
-              :fallback="previewTheme.opacity.input || 1"
+              :fallback="previewTheme.opacity.input"
             />
             <ColorInput
               v-model="inputTextColorLocal"
@@ -316,7 +316,7 @@
             <OpacityInput
               v-model="btnOpacityLocal"
               name="btnOpacity"
-              :fallback="previewTheme.opacity.btn || 1"
+              :fallback="previewTheme.opacity.btn"
             />
             <ColorInput
               v-model="btnTextColorLocal"
@@ -337,7 +337,7 @@
             <OpacityInput
               v-model="borderOpacityLocal"
               name="borderOpacity"
-              :fallback="previewTheme.opacity.border || 1"
+              :fallback="previewTheme.opacity.border"
             />
           </div>
           <div class="color-item">
@@ -363,7 +363,7 @@
             <OpacityInput
               v-model="faintOpacityLocal"
               name="faintOpacity"
-              :fallback="previewTheme.opacity.faint || 0.5"
+              :fallback="previewTheme.opacity.faint"
             />
           </div>
           <div class="color-item">
@@ -377,7 +377,7 @@
             <OpacityInput
               v-model="underlayOpacityLocal"
               name="underlayOpacity"
-              :fallback="previewTheme.opacity.underlay || 0.15"
+              :fallback="previewTheme.opacity.underlay"
             />
           </div>
         </div>
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index df22f94f..e8a64517 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -2,6 +2,8 @@ import { times } from 'lodash'
 import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
 import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
 
+export const CURRENT_VERSION = 3
+
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
 // styles as well as set their own colors in the future.
@@ -150,29 +152,13 @@ const getCssColor = (input, a) => {
   return rgb2rgba({ ...rgb, a })
 }
 
-// Generates a "patch" for theme to make it compatible with v2
-export const generateCompat = (input) => {
-  const { colors } = input
-  const v3compat = {
-    colors: {}
-  }
-  const v2colorsPatch = {}
-
-  // # Link became optional in v3
-  if (typeof colors.link === 'undefined') {
-    v2colorsPatch.link = colors.accent
-    v3compat.colors.link = null
-  }
-
-  return {
-    v3compat,
-    colors: v2colorsPatch
-  }
-}
-
 const generateColors = (themeData) => {
   const colors = {}
   const rawOpacity = Object.assign({
+    panel: 1,
+    btn: 1,
+    border: 1,
+    bg: 1,
     alert: 0.5,
     input: 0.5,
     faint: 0.5,
@@ -207,6 +193,7 @@ const generateColors = (themeData) => {
     } else {
       let value = v
       if (v === 'transparent') {
+        // TODO: hack to keep rest of the code from complaining
         value = '#FF00FF'
       }
       acc[k] = hex2rgb(value)
@@ -221,7 +208,7 @@ const generateColors = (themeData) => {
   const isLightOnDark = convert(colors.bg).hsl.l < convert(colors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  colors.lightText = brightness(20 * mod, colors.text).rgb
+  colors.lightText = col.lightText || brightness(20 * mod, colors.text).rgb
 
   colors.accent = col.accent || col.link
   colors.link = col.link || col.accent
@@ -231,7 +218,8 @@ const generateColors = (themeData) => {
   colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
 
   const underlay = [colors.underlay, opacity.underlay]
-  const fg = [col.fg, opacity.fg]
+  // Technically, foreground can't be transparent (descendants can) but let's keep it just in case
+  const fg = [col.fg, opacity.fg || 1]
   const bg = [col.bg, opacity.bg]
 
   colors.fg = col.fg
@@ -271,16 +259,16 @@ const generateColors = (themeData) => {
 
   colors.alertError = col.alertError || Object.assign({}, colors.cRed)
   const alertError = [colors.alertError, opacity.alert]
-  colors.alertErrorText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
-  colors.alertErrorPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
+  colors.alertErrorText = col.alertErrorText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
+  colors.alertErrorPanelText = col.alertErrorPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
 
   colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
   const alertWarning = [colors.alertWarning, opacity.alert]
-  colors.alertWarningText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
-  colors.alertWarningPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
+  colors.alertWarningText = col.alertWarningText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
+  colors.alertWarningPanelText = col.alertWarningPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
 
   colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
-  colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
+  colors.badgeNotificationText = colors.badgeNotificationText || contrastRatio(colors.badgeNotification).rgb
 
   Object.entries(opacity).forEach(([ k, v ]) => {
     console.log(k)
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 0ed55184..d447005f 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -2,6 +2,218 @@
   "_pleroma_theme_version": 2,
   "name": "Breezy Dark (beta)",
   "theme": {
+    "shadows": {
+      "panel": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "6",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "popup": [
+        {
+          "x": 2,
+          "y": 2,
+          "blur": 3,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.5
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "40",
+          "blur": "40",
+          "spread": "-40",
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.1"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "0",
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": "0.15",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "--accent",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "0",
+          "spread": "50",
+          "color": "--faint",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#FFFFFF",
+          "alpha": "0.2",
+          "inset": true
+        }
+      ]
+    },
+    "colors": {
+      "bg": "#31363b",
+      "underlay": "#000000",
+      "text": "#eff0f1",
+      "lightText": "#ffffff",
+      "accent": "#3daee9",
+      "link": "#3daee9",
+      "faint": "#eff0f1",
+      "lightBg": "#3d4349",
+      "fg": "#31363b",
+      "fgText": "#eff0f1",
+      "fgLink": "#3daee9",
+      "border": "#4c545b",
+      "btn": "#31363b",
+      "btnText": "#eff0f1",
+      "input": "#232629",
+      "inputText": "#ffffff",
+      "panel": "#ff00ff",
+      "panelText": "#eff0f1",
+      "panelLink": "#3daee9",
+      "panelFaint": "#eff0f1",
+      "topBar": "#31363b",
+      "topBarText": "#eff0f1",
+      "topBarLink": "#eff0f1",
+      "faintLink": "#3daee9",
+      "linkBg": "#366681",
+      "icon": "#909396",
+      "cBlue": "#3daee9",
+      "cRed": "#da4453",
+      "cGreen": "#27ae60",
+      "cOrange": "#f67400",
+      "alertError": "#da4453",
+      "alertErrorText": "#eff0f1",
+      "alertErrorPanelText": "#eff0f1",
+      "alertWarning": "#f67400",
+      "alertWarningText": "#eff0f1",
+      "alertWarningPanelText": "#eff0f1",
+      "badgeNotification": "#da4453",
+      "badgeNotificationText": "#ffffff"
+    },
+    "opacity": {
+      "panel": 0,
+      "btn": 1,
+      "border": 1,
+      "bg": 1,
+      "alert": 0.5,
+      "input": 0.5,
+      "faint": 0.5,
+      "underlay": 0.15
+    },
+    "radii": {
+      "btn": "2",
+      "input": "2",
+      "checkbox": "1",
+      "panel": "2",
+      "avatar": "2",
+      "avatarAlt": "2",
+      "tooltip": "2",
+      "attachment": "2"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
     "shadows": {
       "panel": [
         {
@@ -105,21 +317,13 @@
         }
       ]
     },
-    "fonts": {},
-    "opacity": {
-      "input": "1"
-    },
-    "v3compat": {
-      "colors": {
-        "panel": "transparent"
-      }
-    },
+    "opacity": {},
     "colors": {
       "bg": "#31363b",
       "text": "#eff0f1",
       "link": "#3daee9",
       "fg": "#31363b",
-      "panel": "#31363b",
+      "panel": "transparent",
       "input": "#232629",
       "topBarLink": "#eff0f1",
       "btn": "#31363b",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index 5db185dd..243b8593 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -2,6 +2,218 @@
   "_pleroma_theme_version": 2,
   "name": "Breezy Light (beta)",
   "theme": {
+    "shadows": {
+      "panel": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "6",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "popup": [
+        {
+          "x": 2,
+          "y": 2,
+          "blur": 3,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.5
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "40",
+          "blur": "40",
+          "spread": "-40",
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.1"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "0",
+          "spread": "1",
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "--accent",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "0",
+          "spread": "50",
+          "color": "--faint",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#000000",
+          "alpha": "0.2",
+          "inset": true
+        }
+      ]
+    },
+    "colors": {
+      "bg": "#eff0f1",
+      "underlay": "#000000",
+      "text": "#232627",
+      "lightText": "#000000",
+      "accent": "#2980b9",
+      "link": "#2980b9",
+      "faint": "#232627",
+      "lightBg": "#e2e4e6",
+      "fg": "#bcc2c7",
+      "fgText": "#232627",
+      "fgLink": "#2980b9",
+      "border": "#b7bdc3",
+      "btn": "#eff0f1",
+      "btnText": "#232627",
+      "input": "#fcfcfc",
+      "inputText": "#000000",
+      "panel": "#475057",
+      "panelText": "#fcfcfc",
+      "panelLink": "#ffffff",
+      "panelFaint": "#d8dbdc",
+      "topBar": "#475057",
+      "topBarText": "#d8dbdc",
+      "topBarLink": "#eff0f1",
+      "faintLink": "#2980b9",
+      "linkBg": "#a0c4db",
+      "icon": "#898b8c",
+      "cBlue": "#2980b9",
+      "cRed": "#da4453",
+      "cGreen": "#27ae60",
+      "cOrange": "#f67400",
+      "alertError": "#da4453",
+      "alertErrorText": "#232627",
+      "alertErrorPanelText": "#fcfcfc",
+      "alertWarning": "#f67400",
+      "alertWarningText": "#232627",
+      "alertWarningPanelText": "#fcfcfc",
+      "badgeNotification": "#da4453",
+      "badgeNotificationText": "#ffffff"
+    },
+    "opacity": {
+      "panel": 1,
+      "btn": 1,
+      "border": 1,
+      "bg": 1,
+      "alert": 0.5,
+      "input": "1",
+      "faint": 0.5,
+      "underlay": 0.15
+    },
+    "radii": {
+      "btn": "2",
+      "input": "2",
+      "checkbox": "1",
+      "panel": "2",
+      "avatar": "2",
+      "avatarAlt": "2",
+      "tooltip": "2",
+      "attachment": "2"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
     "shadows": {
       "panel": [
         {
@@ -105,20 +317,14 @@
         }
       ]
     },
-    "fonts": {},
     "opacity": {
       "input": "1"
     },
-    "v3compat": {
-      "colors": {
-        "panel": "transparent"
-      }
-    },
     "colors": {
       "bg": "#eff0f1",
       "text": "#232627",
-      "link": "#2980b9",
       "fg": "#bcc2c7",
+      "accent": "#2980b9",
       "panel": "#475057",
       "panelText": "#fcfcfc",
       "input": "#fcfcfc",