commit: f0957bdb4ff1fed060696ac953679cef241e92be
parent 07a48315a14bee16a3b6a1822502301180803e60
Author: Henry Jameson <me@hjkos.com>
Date: Tue, 1 Oct 2024 00:42:33 +0300
palettes that actually work
Diffstat:
12 files changed, 393 insertions(+), 170 deletions(-)
diff --git a/src/components/button.style.js b/src/components/button.style.js
@@ -63,12 +63,18 @@ export default {
}
},
{
- state: ['hover', 'pressed'],
+ state: ['pressed', 'hover'],
directives: {
shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
}
},
{
+ state: ['pressed', 'hover', 'focused'],
+ directives: {
+ shadow: ['--pressedButtonBevel']
+ }
+ },
+ {
state: ['toggled'],
directives: {
background: '--inheritedBackground,-14.2',
diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue
@@ -1,21 +1,73 @@
<template>
<div class="PaletteEditor">
- <ColorInput
- v-for="key in paletteKeys"
- :key="key"
- :model-value="props.modelValue[key]"
- :fallback="fallback(key)"
- :label="$t('settings.style.themes3.editor.palette.' + key)"
- @update:modelValue="value => updatePalette(key, value)"
- />
+ <div class="colors">
+ <ColorInput
+ v-for="key in paletteKeys"
+ :key="key"
+ :model-value="props.modelValue[key]"
+ :fallback="fallback(key)"
+ :label="$t('settings.style.themes3.palette.' + key)"
+ @update:modelValue="value => updatePalette(key, value)"
+ />
+ </div>
+ <div class="controls">
+ <button
+ class="btn button-default"
+ @click="importPalette"
+ >
+ <FAIcon icon="file-import"/>
+ {{ $t('settings.style.themes3.palette.import') }}
+ </button>
+ <button
+ class="btn button-default"
+ @click="exportPalette"
+ >
+ <FAIcon icon="file-export"/>
+ {{ $t('settings.style.themes3.palette.export') }}
+ </button>
+ </div>
</div>
</template>
<script setup>
import ColorInput from 'src/components/color_input/color_input.vue'
+import {
+ // newImporter,
+ newExporter
+} from 'src/services/export_import/export_import.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faFileImport,
+ faFileExport
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faFileImport,
+ faFileExport
+)
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
+const paletteExporter = newExporter({
+ filename: 'pleroma.palette.json',
+ getExportedObject: () => props.modelValue
+})
+/*
+ const themeImporter = newImporter({
+ validator: importValidator,
+ onImport,
+ onImportFailure,
+ })
+*/
+
+const exportPalette = () => {
+ paletteExporter.exportData()
+}
+
+const importPalette = () => {
+ // TODO
+}
const paletteKeys = [
'bg',
@@ -40,7 +92,7 @@ const fallback = (key) => {
return props.modelValue.accent
}
if (key.startsWith('extra')) {
- return '#000000'
+ return '#008080'
}
}
@@ -54,10 +106,18 @@ const updatePalette = (paletteKey, value) => {
<style lang="scss">
.PaletteEditor {
- display: grid;
- justify-content: space-around;
- grid-template-columns: repeat(4, min-content);
- grid-template-rows: repeat(auto-fit, min-content);
- grid-gap: 0.5em;
+ .colors {
+ display: grid;
+ justify-content: space-around;
+ grid-template-columns: repeat(4, min-content);
+ grid-template-rows: repeat(auto-fit, min-content);
+ grid-gap: 0.5em;
+ }
+
+ .controls {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 0.5em;
+ }
}
</style>
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
@@ -9,7 +9,7 @@ import FontControl from 'src/components/font_control/font_control.vue'
import { normalizeThemeData } from 'src/modules/interface'
import {
- getThemes
+ getThemeResources
} from 'src/services/style_setter/style_setter.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
@@ -35,6 +35,17 @@ const AppearanceTab = {
data () {
return {
availableStyles: [],
+ availablePalettes: [],
+ palettesKeys: [
+ 'background',
+ 'foreground',
+ 'link',
+ 'text',
+ 'cRed',
+ 'cBlue',
+ 'cGreen',
+ 'cOrange'
+ ],
intersectionObserver: null,
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
key: mode,
@@ -64,29 +75,36 @@ const AppearanceTab = {
Preview
},
mounted () {
- getThemes()
- .then((promises) => {
- return Promise.all(
- Object.entries(promises)
- .map(([k, v]) => v.then(res => [k, res]))
- )
+ getThemeResources('/static/styles.json')
+ .then((themes) => {
+ this.availableStyles = Object
+ .entries(themes)
+ .map(([key, data]) => ({ key, data, name: data.name || data[0], version: 'v2' }))
})
- .then(themes => themes.reduce((acc, [k, v]) => {
- if (v) {
- return [
- ...acc,
- {
- name: v.name || v[0],
- key: k,
- data: v
- }
- ]
- } else {
- return acc
- }
- }, []))
- .then((themesComplete) => {
- this.availableStyles = themesComplete
+
+ getThemeResources('/static/palettes/index.json')
+ .then((palettes) => {
+ const result = {}
+ console.log(palettes)
+ Object.entries(palettes).forEach(([k, v]) => {
+ if (Array.isArray(v)) {
+ const [
+ name,
+ background,
+ foreground,
+ text,
+ link,
+ cRed = '#FF0000',
+ cBlue = '#0000FF',
+ cGreen = '#00FF00',
+ cOrange = '#E3FF00'
+ ] = v
+ result[k] = { name, background, foreground, text, link, cRed, cBlue, cGreen, cOrange }
+ } else {
+ result[k] = v
+ }
+ })
+ this.availablePalettes = result
})
if (window.IntersectionObserver) {
@@ -171,18 +189,30 @@ const AppearanceTab = {
setTheme (name) {
this.$store.dispatch('setTheme', { themeName: name, saveData: true, recompile: true })
},
+ setPalette (name) {
+ this.$store.dispatch('setPalette', { paletteData: name })
+ },
previewTheme (key, input) {
- const style = normalizeThemeData(input)
- const x = 2
- if (x === 1) return
- const theme2 = convertTheme2To3(style)
- const theme3 = init({
- inputRuleset: theme2,
- ultimateBackgroundColor: '#000000',
- liteMode: true,
- debug: true,
- onlyNormalState: true
- })
+ let theme3
+ if (input) {
+ const style = normalizeThemeData(input)
+ const theme2 = convertTheme2To3(style)
+ theme3 = init({
+ inputRuleset: theme2,
+ ultimateBackgroundColor: '#000000',
+ liteMode: true,
+ debug: true,
+ onlyNormalState: true
+ })
+ } else {
+ theme3 = init({
+ inputRuleset: [],
+ ultimateBackgroundColor: '#000000',
+ liteMode: true,
+ debug: true,
+ onlyNormalState: true
+ })
+ }
return getScopedVersion(
getCssRules(theme3.eager),
diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss
@@ -0,0 +1,76 @@
+.appearance-tab {
+ .palette,
+ .theme-notice {
+ padding: 0.5em;
+ margin: 1em;
+ }
+
+ .palettes {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 0.5em;
+ }
+
+ .palette-entry {
+ display: flex;
+ align-items: center;
+
+ > label {
+ flex: 1 0 auto;
+ }
+
+ .palette-square {
+ flex: 0 0 auto;
+ display: inline-block;
+ min-width: 1em;
+ min-height: 1em;
+ }
+ }
+
+ .column-settings {
+ display: flex;
+ justify-content: space-evenly;
+ flex-wrap: wrap;
+ }
+
+ .column-settings .size-label {
+ display: block;
+ margin-bottom: 0.5em;
+ margin-top: 0.5em;
+ }
+
+ .theme-list {
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ margin: -0.5em 0;
+ height: 25em;
+ overflow-x: hidden;
+ overflow-y: auto;
+ scrollbar-gutter: stable;
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
+ padding: 0;
+
+ .theme-preview {
+ font-size: 1rem; // fix for firefox
+ width: 19rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 0.5em;
+
+ &.placeholder {
+ opacity: 0.2;
+ }
+
+ .theme-preview-container {
+ pointer-events: none;
+ zoom: 0.5;
+ border: none;
+ border-radius: var(--roundness);
+ text-align: left;
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue
@@ -7,12 +7,32 @@
ref="themeList"
>
<button
+ class="button-default theme-preview"
+ data-theme-key="stock"
+ @click="setTheme(null)"
+ >
+ <!-- eslint-disable vue/no-v-text-v-html-on-component -->
+ <component
+ :is="'style'"
+ v-html="previewTheme('stock')"
+ />
+ <!-- eslint-enable vue/no-v-text-v-html-on-component -->
+ <preview />
+ <h4 class="theme-name">
+ {{ $t('settings.style.stock_theme_used') }}
+ <span class="alert neutral version">v3</span>
+ </h4>
+ </button>
+ <button
v-if="isCustomThemeUsed"
disabled
class="button-default theme-preview"
>
<preview />
- <h4 class="theme-name">{{ $t('settings.style.custom_theme_used') }}</h4>
+ <h4 class="theme-name">
+ {{ $t('settings.style.custom_theme_used') }}
+ <span class="alert neutral version">v2</span>
+ </h4>
</button>
<button
v-for="style in availableStyles"
@@ -30,9 +50,31 @@
/>
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview :class="{ placeholder: ready }" :id="'theme-preview-' + style.key"/>
- <h4 class="theme-name">{{ style.name }}</h4>
+ <h4 class="theme-name">
+ {{ style.name }}
+ <span class="alert neutral version">{{ style.version }}</span>
+ </h4>
</button>
</ul>
+ <h3>{{ $t('settings.style.themes3.palette.label') }}</h3>
+ <div class="palettes">
+ <button
+ v-for="p in availablePalettes"
+ class="btn button-default palette-entry"
+ :key="p.name"
+ @click="() => setPalette(p)"
+ >
+ <label>
+ {{ p.name }}
+ </label>
+ <span
+ v-for="c in palettesKeys"
+ :key="c"
+ class="palette-square"
+ :style="{ backgroundColor: p[c], border: '1px solid ' + (p[c] ?? 'var(--text)') }"
+ />
+ </button>
+ </div>
</div>
<div class="alert neutral theme-notice">
{{ $t("settings.style.appearance_tab_note") }}
@@ -256,58 +298,4 @@
<script src="./appearance_tab.js"></script>
-<style lang="scss">
-.appearance-tab {
- .theme-notice {
- padding: 0.5em;
- margin: 1em;
- }
-
- .column-settings {
- display: flex;
- justify-content: space-evenly;
- flex-wrap: wrap;
- }
-
- .column-settings .size-label {
- display: block;
- margin-bottom: 0.5em;
- margin-top: 0.5em;
- }
-
- .theme-list {
- list-style: none;
- display: flex;
- flex-wrap: wrap;
- margin: -0.5em 0;
- height: 25em;
- overflow-x: hidden;
- overflow-y: auto;
- scrollbar-gutter: stable;
- border-radius: var(--roundness);
- border: 1px solid var(--border);
- padding: 0;
-
- .theme-preview {
- font-size: 1rem; // fix for firefox
- width: 19rem;
- display: flex;
- flex-direction: column;
- align-items: center;
- margin: 0.5em;
-
- &.placeholder {
- opacity: 0.2;
- }
-
- .theme-preview-container {
- pointer-events: none;
- zoom: 0.5;
- border: none;
- border-radius: var(--roundness);
- text-align: left;
- }
- }
- }
-}
-</style>
+<style lang="scss" src="./appearance_tab.scss"></style>
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue
@@ -54,7 +54,7 @@
<div class="setting-item palette-editor">
<div class="label">
<label for="palette-selector">
- {{ $t('settings.style.themes3.editor.palette.label') }}
+ {{ $t('settings.style.themes3.palette.label') }}
{{ ' ' }}
</label>
<Select
@@ -62,10 +62,10 @@
id="palette-selector"
>
<option key="dark" value="dark">
- {{ $t('settings.style.themes3.editor.palette.dark') }}
+ {{ $t('settings.style.themes3.palette.dark') }}
</option>
<option key="light" value="light">
- {{ $t('settings.style.themes3.editor.palette.light') }}
+ {{ $t('settings.style.themes3.palette.light') }}
</option>
</Select>
</div>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -5,7 +5,7 @@ import {
relativeLuminance
} from 'src/services/color_convert/color_convert.js'
import {
- getThemes
+ getThemeResources
} from 'src/services/style_setter/style_setter.js'
import {
newImporter,
@@ -125,25 +125,9 @@ export default {
created () {
const self = this
- getThemes()
- .then((promises) => {
- return Promise.all(
- Object.entries(promises)
- .map(([k, v]) => v.then(res => [k, res]))
- )
- })
- .then(themes => themes.reduce((acc, [k, v]) => {
- if (v) {
- return {
- ...acc,
- [k]: v
- }
- } else {
- return acc
- }
- }, {}))
+ getThemeResources('/static/styles.json')
.then((themesComplete) => {
- self.availableStyles = themesComplete
+ self.availableStyles = Object.values(themesComplete)
})
},
mounted () {
@@ -412,9 +396,6 @@ export default {
forceUseSource = false
) {
this.dismissWarning()
- if (!source && !theme) {
- throw new Error('Can\'t load theme: empty')
- }
const version = (origin === 'localStorage' && !theme.colors)
? 'l1'
: fileVersion
@@ -494,14 +475,7 @@ export default {
customTheme: theme,
customThemeSource: source
} = this.$store.getters.mergedConfig
- if (!theme && !source) {
- // Anon user or never touched themes
- this.loadTheme(
- this.$store.state.instance.themeData,
- 'defaults',
- confirmLoadSource
- )
- } else {
+ if (theme || source) {
this.loadTheme(
{
theme,
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -748,11 +748,31 @@
"more_settings": "More settings",
"style": {
"custom_theme_used": "(Custom theme)",
+ "stock_theme_used": "(Stock theme)",
"themes2_outdated": "Editor for Themes V2 is being phased out and will eventually be replaced with a new one that takes advantage of new Themes V3 engine. It should still work but experience might be degraded and inconsistent.",
"appearance_tab_note": "Changes on this tab do not affect the theme used, so exported theme will be different from what seen in the UI",
"update_preview": "Update preview",
"themes3": {
"define": "Override",
+ "palette": {
+ "label": "Palette",
+ "import": "Import",
+ "export": "Export",
+ "dark": "Dark mode",
+ "light": "Light mode",
+ "bg": "Panel background",
+ "fg": "Buttons etc.",
+ "text": "Text",
+ "link": "Links",
+ "accent": "Accent color",
+ "cRed": "Red color",
+ "cBlue": "Blue color",
+ "cGreen": "Green color",
+ "cOrange": "Orange color",
+ "extra1": "Extra 1",
+ "extra2": "Extra 2",
+ "extra3": "Extra 3"
+ },
"editor": {
"title": "Style",
"new_style": "New",
@@ -778,23 +798,6 @@
"preserve": "Keep color",
"no-auto": "Disabled"
},
- "palette": {
- "label": "Palette",
- "dark": "Dark mode",
- "light": "Light mode",
- "bg": "Panel background",
- "fg": "Buttons etc.",
- "text": "Text",
- "link": "Links",
- "accent": "Accent color",
- "cRed": "Red color",
- "cBlue": "Blue color",
- "cGreen": "Green color",
- "cOrange": "Orange color",
- "extra1": "Extra 1",
- "extra2": "Extra 2",
- "extra3": "Extra 3"
- },
"components": {
"normal": {
"state": "Normal",
diff --git a/src/modules/interface.js b/src/modules/interface.js
@@ -212,6 +212,11 @@ const interfaceMod = {
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
},
+ setPalette ({ dispatch, commit }, { paletteData }) {
+ console.log('PAL', paletteData)
+ commit('setOption', { name: 'userPalette', value: paletteData })
+ dispatch('setTheme', { themeName: null, recompile: true })
+ },
setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) {
const {
theme: instanceThemeName
@@ -221,21 +226,52 @@ const interfaceMod = {
theme: userThemeName,
customTheme: userThemeSnapshot,
customThemeSource: userThemeSource,
+ userPalette,
forceThemeRecompilation,
themeDebug,
theme3hacks
} = rootState.config
+ const userPaletteIss = (() => {
+ if (!userPalette) return null
+ const result = {
+ component: 'Root',
+ directives: {}
+ }
+
+ Object
+ .entries(userPalette)
+ .filter(([k]) => k !== 'name')
+ .forEach(([k, v]) => {
+ let issRootDirectiveName
+ switch (k) {
+ case 'background':
+ issRootDirectiveName = 'bg'
+ break
+ case 'foreground':
+ issRootDirectiveName = 'fg'
+ break
+ default:
+ issRootDirectiveName = k
+ }
+ result.directives['--' + issRootDirectiveName] = 'color | ' + v
+ })
+ return result
+ })()
const actualThemeName = userThemeName || instanceThemeName
const forceRecompile = forceThemeRecompilation || recompile
let promise = null
+ console.log('TEST', actualThemeName, themeData)
+
if (themeData) {
promise = Promise.resolve(normalizeThemeData(themeData))
} else if (themeName) {
promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
+ } else if (themeName === null) {
+ promise = Promise.resolve(null)
} else if (userThemeSource || userThemeSnapshot) {
promise = Promise.resolve(normalizeThemeData({
_pleroma_theme_version: 2,
@@ -264,13 +300,14 @@ const interfaceMod = {
promise
.then(realThemeData => {
- const theme2ruleset = convertTheme2To3(realThemeData)
+ const theme2ruleset = realThemeData ? convertTheme2To3(realThemeData) : null
if (saveData) {
commit('setOption', { name: 'theme', value: themeName || actualThemeName })
commit('setOption', { name: 'customTheme', value: realThemeData })
commit('setOption', { name: 'customThemeSource', value: realThemeData })
}
+
const hacks = []
Object.entries(theme3hacks).forEach(([key, value]) => {
@@ -336,9 +373,15 @@ const interfaceMod = {
})
const ruleset = [
- ...theme2ruleset,
...hacks
]
+ if (!theme2ruleset && userPaletteIss) {
+ ruleset.unshift(userPaletteIss)
+ }
+
+ if (theme2ruleset) {
+ ruleset.unshift(...theme2ruleset)
+ }
applyTheme(
ruleset,
@@ -355,6 +398,7 @@ const interfaceMod = {
export default interfaceMod
export const normalizeThemeData = (input) => {
+ console.log(input)
if (Array.isArray(input)) {
const themeData = { colors: {} }
themeData.colors.bg = input[1]
@@ -365,7 +409,7 @@ export const normalizeThemeData = (input) => {
themeData.colors.cGreen = input[6]
themeData.colors.cBlue = input[7]
themeData.colors.cOrange = input[8]
- return generatePreset(themeData).theme
+ return generatePreset(themeData).source || generatePreset(themeData).theme
}
let themeData, themeSource
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -256,13 +256,13 @@ export const applyConfig = (input) => {
body.classList.remove('hidden')
}
-export const getThemes = () => {
+export const getThemeResources = (url) => {
const cache = 'no-store'
- return window.fetch('/static/styles.json', { cache })
+ return window.fetch(url, { cache })
.then((data) => data.json())
- .then((themes) => {
- return Object.entries(themes).map(([k, v]) => {
+ .then((resources) => {
+ return Object.entries(resources).map(([k, v]) => {
let promise = null
if (typeof v === 'object') {
promise = Promise.resolve(v)
@@ -284,10 +284,26 @@ export const getThemes = () => {
return acc
}, {})
})
+ .then((promises) => {
+ return Promise.all(
+ Object.entries(promises)
+ .map(([k, v]) => v.then(res => [k, res]))
+ )
+ })
+ .then(themes => themes.reduce((acc, [k, v]) => {
+ if (v) {
+ return {
+ ...acc,
+ [k]: v
+ }
+ } else {
+ return acc
+ }
+ }, {}))
}
export const getPreset = (val) => {
- return getThemes()
+ return getThemeResources('/static/styles.json')
.then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
.then((theme) => {
const isV1 = Array.isArray(theme)
diff --git a/static/palettes/index.json b/static/palettes/index.json
@@ -0,0 +1,32 @@
+{
+ "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+ "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+ "classic-dark": {
+ "name": "Classic Dark",
+ "background": "#161c20",
+ "foreground": "#282e32",
+ "text": "#b9b9b9",
+ "link": "#baaa9c",
+ "cRed": "#d31014",
+ "cBlue": "#0fa00f",
+ "cGreen": "#0095ff",
+ "cOrange": "#ffa500"
+ },
+ "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
+ "tomorrow-night": {
+ "name": "Tomorrow Night",
+ "background": "#1d1f21",
+ "foreground": "#373b41",
+ "link": "#81a2be",
+ "text": "#c5c8c6",
+ "cRed": "#cc6666",
+ "cBlue": "#8abeb7",
+ "cGreen": "#b5bd68",
+ "cOrange": "#de935f",
+ "_cYellow": "#f0c674",
+ "_cPurple": "#b294bb"
+ },
+ "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
+ "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
+ "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ]
+}
diff --git a/static/styles.json b/static/styles.json
@@ -1,12 +1,6 @@
{
"pleroma-dark": "/static/themes/pleroma-dark.json",
"pleroma-light": "/static/themes/pleroma-light.json",
- "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
- "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
- "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
- "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
- "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
-
"redmond-xx": "/static/themes/redmond-xx.json",
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
"redmond-xxi": "/static/themes/redmond-xxi.json",