logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: a8acba8cb2c639a3de3764df4226960458f996b8
parent: fb5261b926adfb5b9bbe1bf55e36fe8b5f4eb57f
Author: HJ <spam@hjkos.com>
Date:   Tue, 11 Dec 2018 20:35:19 +0000

Merge branch 'feature/theming2' into 'develop'

Themes v2

See merge request pleroma/pleroma-fe!377

Diffstat:

Mpackage.json1+
Msrc/App.js7++++++-
Msrc/App.scss192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/_variables.scss11+++++------
Msrc/components/chat_panel/chat_panel.vue4++--
Asrc/components/color_input/color_input.vue53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/contrast_ratio/contrast_ratio.vue69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/delete_button/delete_button.vue2+-
Asrc/components/export_import/export_import.vue87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/font_control/font_control.js58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/font_control/font_control.vue54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/notification/notification.js3++-
Msrc/components/notification/notification.vue2+-
Msrc/components/notifications/notifications.scss58++++++++++++++++++++++++++++++++--------------------------
Msrc/components/notifications/notifications.vue3++-
Asrc/components/opacity_input/opacity_input.vue38++++++++++++++++++++++++++++++++++++++
Msrc/components/post_status_form/post_status_form.vue14++++++++------
Asrc/components/range_input/range_input.vue48++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/settings/settings.vue12+++++-------
Asrc/components/shadow_control/shadow_control.js87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/shadow_control/shadow_control.vue243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/status/status.js3++-
Msrc/components/status/status.vue28++++++++++++++++++++++++----
Asrc/components/style_switcher/preview.vue78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/style_switcher/style_switcher.js672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Asrc/components/style_switcher/style_switcher.scss335+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/style_switcher/style_switcher.vue538++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/components/tab_switcher/tab_switcher.jsx13++++++++-----
Msrc/components/tab_switcher/tab_switcher.scss36+++++++++++++++++++++++++++++-------
Msrc/components/timeline/timeline.vue16++++------------
Msrc/components/user_card_content/user_card_content.js10+++++++---
Msrc/components/user_card_content/user_card_content.vue159++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/i18n/en.json112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/i18n/ru.json114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/lib/persisted_state.js1+
Msrc/modules/config.js6+++---
Msrc/modules/instance.js4++--
Msrc/modules/interface.js6++++++
Msrc/services/color_convert/color_convert.js110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/services/style_setter/style_setter.js495++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/services/user_highlighter/user_highlighter.js2+-
Mstatic/font/config.json39+++++++++++++++++++++++++++++++--------
Mstatic/font/css/fontello-codes.css8++++++--
Mstatic/font/css/fontello-embedded.css20++++++++++++--------
Mstatic/font/css/fontello-ie7-codes.css8++++++--
Mstatic/font/css/fontello-ie7.css8++++++--
Mstatic/font/css/fontello.css22+++++++++++++---------
Mstatic/font/demo.html26++++++++++++++++----------
Mstatic/font/font/fontello.eot0
Mstatic/font/font/fontello.svg12++++++++++--
Mstatic/font/font/fontello.ttf0
Mstatic/font/font/fontello.woff0
Mstatic/font/font/fontello.woff20
Mstatic/styles.json8+++++++-
Astatic/themes/breezy-dark.json139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/themes/breezy-light.json139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/themes/redmond-xx-se.json297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/themes/redmond-xx.json288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/themes/redmond-xxi.json270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Myarn.lock4++++
60 files changed, 4336 insertions(+), 736 deletions(-)

diff --git a/package.json b/package.json @@ -16,6 +16,7 @@ "dependencies": { "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-lodash": "^3.2.11", + "chromatism": "^3.0.0", "diff": "^3.0.1", "karma-mocha-reporter": "^2.2.1", "localforage": "^1.5.0", diff --git a/src/App.js b/src/App.js @@ -59,7 +59,12 @@ export default { }) }, logo () { return this.$store.state.instance.logo }, - style () { return { 'background-image': `url(${this.background})` } }, + style () { + return { + '--body-background-image': `url(${this.background})`, + 'background-image': `url(${this.background})` + } + }, sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, diff --git a/src/App.scss b/src/App.scss @@ -34,10 +34,11 @@ h4 { body { font-family: sans-serif; + font-family: var(--interfaceFont, sans-serif); font-size: 14px; margin: 0; - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); max-width: 100vw; overflow-x: hidden; } @@ -50,19 +51,24 @@ a { button { user-select: none; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: $fallback--text; + color: var(--btnText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btn, $fallback--fg); border: none; border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - border-top: 1px solid rgba(255, 255, 255, 0.2); - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - box-shadow: 0px 0px 2px black; + box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + box-shadow: var(--buttonShadow); font-size: 14px; font-family: sans-serif; + font-family: var(--interfaceFont, sans-serif); + + i[class*=icon-] { + color: $fallback--text; + color: var(--btnText, $fallback--text); + } &::-moz-focus-inner { border: none; @@ -70,11 +76,12 @@ button { &:hover { box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3); + box-shadow: var(--buttonHoverShadow); } &:active { - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); + box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset; + box-shadow: var(--buttonPressedShadow); } &:disabled { @@ -99,32 +106,37 @@ input, textarea, .select { border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); - box-shadow: 0px 0px 2px black inset; - background-color: $fallback--input; - background-color: var(--input, $fallback--input); - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 0px 2px 0px rgba(0, 0, 0, 1) inset; + box-shadow: var(--inputShadow); + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); + color: $fallback--lightText; + color: var(--inputText, $fallback--lightText); font-family: sans-serif; + font-family: var(--inputFont, sans-serif); font-size: 14px; - padding: 8px 7px; + padding: 8px .5em; box-sizing: border-box; display: inline-block; position: relative; - height: 29px; + height: 28px; line-height: 16px; hyphens: none; + &:disabled, &[disabled=disabled] { + cursor: not-allowed; + opacity: 0.5; + } + .icon-down-open { position: absolute; top: 0; bottom: 0; right: 5px; height: 100%; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - line-height: 29px; + color: $fallback--text; + color: var(--text, $fallback--text); + line-height: 28px; z-index: 0; pointer-events: none; } @@ -135,22 +147,33 @@ input, textarea, .select { appearance: none; background: transparent; border: none; + color: $fallback--text; + color: var(--text, $fallback--text); margin: 0; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - padding: 4px 2em 3px 3px; + padding: 0 2em 0 .2em; + font-family: sans-serif; + font-family: var(--inputFont, sans-serif); + font-size: 14px; width: 100%; z-index: 1; - height: 29px; + height: 28px; line-height: 16px; } + &[type=range] { + background: none; + border: none; + margin: 0; + box-shadow: none; + flex: 1; + } + &[type=radio], &[type=checkbox] { display: none; &:checked + label::before { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } &:disabled, { @@ -166,14 +189,13 @@ input, textarea, .select { transition: color 200ms; width: 1.1em; height: 1.1em; - border-radius: $fallback--checkBoxRadius; - border-radius: var(--checkBoxRadius, $fallback--checkBoxRadius); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); + border-radius: $fallback--checkboxRadius; + border-radius: var(--checkboxRadius, $fallback--checkboxRadius); box-shadow: 0px 0px 2px black inset; + box-shadow: var(--inputShadow); margin-right: .5em; - background-color: $fallback--input; - background-color: var(--input, $fallback--input); + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); vertical-align: top; text-align: center; line-height: 1.1em; @@ -187,8 +209,8 @@ input, textarea, .select { } option { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); background-color: $fallback--bg; background-color: var(--bg, $fallback--bg); } @@ -254,7 +276,7 @@ nav { mask-position: center; mask-size: contain; background-color: $fallback--fg; - background-color: var(--fg, $fallback--fg); + background-color: var(--topBarText, $fallback--fg); position: absolute; top: 0; bottom: 0; @@ -279,9 +301,9 @@ nav { margin: auto; height: 50px; - a i { + a, a i { color: $fallback--link; - color: var(--link, $fallback--link); + color: var(--topBarLink, $fallback--link); } } } @@ -304,15 +326,33 @@ main-router { .panel { display: flex; + position: relative; + flex-direction: column; margin: 0.5em; background-color: $fallback--bg; background-color: var(--bg, $fallback--bg); - border-radius: $fallback--panelRadius; - border-radius: var(--panelRadius, $fallback--panelRadius); - box-shadow: 1px 1px 4px rgba(0,0,0,.6); + &::after, & { + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + } + + &::after { + content: ''; + position: absolute; + + top: 0; + bottom: 0; + left: 0; + right: 0; + + pointer-events: none; + + box-shadow: 1px 1px 4px rgba(0,0,0,.6); + box-shadow: var(--panelShadow); + } } .panel-body:empty::before { @@ -330,15 +370,23 @@ main-router { padding: .6em .6em; text-align: left; line-height: 28px; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: var(--panelText); + background-color: $fallback--fg; + background-color: var(--panel, $fallback--fg); align-items: baseline; + box-shadow: var(--panelHeaderShadow); .title { flex: 1 0 auto; font-size: 1.3em; } + .faint { + background-color: transparent; + color: $fallback--faint; + color: var(--panelFaint, $fallback--faint); + } + .alert { white-space: nowrap; text-overflow: ellipsis; @@ -359,6 +407,11 @@ main-router { min-width: 1px; align-self: stretch; } + + a { + color: $fallback--link; + color: var(--panelLink, $fallback--link) + } } .panel-heading.stub { @@ -369,6 +422,11 @@ main-router { .panel-footer { border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + + a { + color: $fallback--link; + color: var(--panelLink, $fallback--link) + } } .panel-body > p { @@ -387,11 +445,13 @@ main-router { nav { z-index: 1000; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: var(--topBarText); + background-color: $fallback--fg; + background-color: var(--topBar, $fallback--fg); color: $fallback--faint; color: var(--faint, $fallback--faint); box-shadow: 0px 0px 4px rgba(0,0,0,.6); + box-shadow: var(--topBarShadow); } .fade-enter-active, .fade-leave-active { @@ -465,20 +525,46 @@ nav { flex-grow: 0; } } +.badge { + display: inline-block; + border-radius: 99px; + min-width: 22px; + max-width: 22px; + min-height: 22px; + max-height: 22px; + font-size: 15px; + line-height: 22px; + text-align: center; + vertical-align: middle; + white-space: nowrap; + padding: 0; + + &.badge-notification { + background-color: $fallback--cRed; + background-color: var(--badgeNotification, $fallback--cRed); + color: white; + color: var(--badgeNotificationText, white); + } +} .alert { margin: 0.35em; padding: 0.25em; border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - color: $fallback--faint; - color: var(--faint, $fallback--faint); min-height: 28px; line-height: 28px; &.error { - background-color: $fallback--cAlertRed; - background-color: var(--cAlertRed, $fallback--cAlertRed); + background-color: $fallback--alertError; + background-color: var(--alertError, $fallback--alertError); + color: $fallback--text; + color: var(--alertErrorText, $fallback--text); + + .panel-heading & { + color: $fallback--text; + color: var(--alertErrorPanelText, $fallback--text); + } } } @@ -516,8 +602,8 @@ nav { cursor: pointer; .selected { - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); } .text-format { diff --git a/src/_variables.scss b/src/_variables.scss @@ -3,24 +3,23 @@ $main-background: white; $darkened-background: whitesmoke; $fallback--bg: #121a24; -$fallback--btn: #182230; -$fallback--input: #182230; +$fallback--fg: #182230; $fallback--faint: rgba(185, 185, 186, .5); -$fallback--fg: #b9b9ba; +$fallback--text: #b9b9ba; $fallback--link: #d8a070; $fallback--icon: #666; $fallback--lightBg: rgb(21, 30, 42); -$fallback--lightFg: #b9b9ba; +$fallback--lightText: #b9b9ba; $fallback--border: #222; $fallback--cRed: #ff0000; $fallback--cBlue: #0095ff; $fallback--cGreen: #0fa00f; $fallback--cOrange: orange; -$fallback--cAlertRed: rgba(211,16,20,.5); +$fallback--alertError: rgba(211,16,20,.5); $fallback--panelRadius: 10px; -$fallback--checkBoxRadius: 2px; +$fallback--checkboxRadius: 2px; $fallback--btnRadius: 4px; $fallback--inputRadius: 4px; $fallback--tooltipRadius: 5px; diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue @@ -55,8 +55,8 @@ .chat-heading { cursor: pointer; .icon-comment-empty { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } } diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue @@ -0,0 +1,53 @@ +<template> +<div class="color-control style-control" :class="{ disabled: !present || disabled }"> + <label :for="name" class="label"> + {{label}} + </label> + <input + v-if="typeof fallback !== 'undefined'" + class="opt exlcude-disabled" + :id="name + '-o'" + type="checkbox" + :checked="present" + @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"> + <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> + <input + :id="name" + class="color-input" + type="color" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + > + <input + :id="name + '-t'" + class="text-input" + type="text" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + > +</div> +</template> + +<script> +export default { + props: [ + 'name', 'label', 'value', 'fallback', 'disabled' + ], + computed: { + present () { + return typeof this.value !== 'undefined' + } + } +} +</script> + +<style lang="scss"> +.color-control { + input.text-input { + max-width: 7em; + flex: 1; + } +} +</style> diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue @@ -0,0 +1,69 @@ +<template> +<span v-if="contrast" class="contrast-ratio"> + <span :title="hint" class="rating"> + <span v-if="contrast.aaa"> + <i class="icon-thumbs-up-alt"/> + </span> + <span v-if="!contrast.aaa && contrast.aa"> + <i class="icon-adjust"/> + </span> + <span v-if="!contrast.aaa && !contrast.aa"> + <i class="icon-attention"/> + </span> + </span> + <span class="rating" v-if="contrast && large" :title="hint_18pt"> + <span v-if="contrast.laaa"> + <i class="icon-thumbs-up-alt"/> + </span> + <span v-if="!contrast.laaa && contrast.laa"> + <i class="icon-adjust"/> + </span> + <span v-if="!contrast.laaa && !contrast.laa"> + <i class="icon-attention"/> + </span> + </span> +</span> +</template> + +<script> +export default { + props: [ + 'large', 'contrast' + ], + computed: { + hint () { + const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad') + const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) + const context = this.$t('settings.style.common.contrast.context.text') + const ratio = this.contrast.text + return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) + }, + hint_18pt () { + const levelVal = this.contrast.laaa ? 'aaa' : (this.contrast.laa ? 'aa' : 'bad') + const level = this.$t(`settings.style.common.contrast.level.${levelVal}`) + const context = this.$t('settings.style.common.contrast.context.18pt') + const ratio = this.contrast.text + return this.$t('settings.style.common.contrast.hint', { level, context, ratio }) + } + } +} +</script> + +<style lang="scss"> +.contrast-ratio { + display: flex; + justify-content: flex-end; + + margin-top: -4px; + margin-bottom: 5px; + + .label { + margin-right: 1em; + } + + .rating { + display: inline-block; + text-align: center; + } +} +</style> diff --git a/src/components/delete_button/delete_button.vue b/src/components/delete_button/delete_button.vue @@ -14,8 +14,8 @@ .icon-cancel,.delete-status { cursor: pointer; &:hover { - color: var(--cRed, $fallback--cRed); color: $fallback--cRed; + color: var(--cRed, $fallback--cRed); } } </style> diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue @@ -0,0 +1,87 @@ +<template> +<div class="import-export-container"> + <slot name="before"/> + <button class="btn" @click="exportData">{{ exportLabel }}</button> + <button class="btn" @click="importData">{{ importLabel }}</button> + <slot name="afterButtons"/> + <p v-if="importFailed" class="alert error">{{ importFailedText }}</p> + <slot name="afterError"/> +</div> +</template> + +<script> +export default { + props: [ + 'exportObject', + 'importLabel', + 'exportLabel', + 'importFailedText', + 'validator', + 'onImport', + 'onImportFailure' + ], + data () { + return { + importFailed: false + } + }, + methods: { + exportData () { + const stringified = JSON.stringify(this.exportObject) // Pretty-print and indent with 2 spaces + + // Create an invisible link with a data url and simulate a click + const e = document.createElement('a') + e.setAttribute('download', 'pleroma_theme.json') + e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) + e.style.display = 'none' + + document.body.appendChild(e) + e.click() + document.body.removeChild(e) + }, + importData () { + this.importFailed = false + const filePicker = document.createElement('input') + filePicker.setAttribute('type', 'file') + filePicker.setAttribute('accept', '.json') + + filePicker.addEventListener('change', event => { + if (event.target.files[0]) { + // eslint-disable-next-line no-undef + const reader = new FileReader() + reader.onload = ({target}) => { + try { + const parsed = JSON.parse(target.result) + const valid = this.validator(parsed) + if (valid) { + this.onImport(parsed) + } else { + this.importFailed = true + // this.onImportFailure(valid) + } + } catch (e) { + // This will happen both if there is a JSON syntax error or the theme is missing components + this.importFailed = true + // this.onImportFailure(e) + } + } + reader.readAsText(event.target.files[0]) + } + }) + + document.body.appendChild(filePicker) + filePicker.click() + document.body.removeChild(filePicker) + } + } +} +</script> + +<style lang="scss"> +.import-export-container { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: center; +} +</style> diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js @@ -0,0 +1,58 @@ +import { set } from 'vue' + +export default { + props: [ + 'name', 'label', 'value', 'fallback', 'options', 'no-inherit' + ], + data () { + return { + lValue: this.value, + availableOptions: [ + this.noInherit ? '' : 'inherit', + 'custom', + ...(this.options || []), + 'serif', + 'monospace', + 'sans-serif' + ].filter(_ => _) + } + }, + beforeUpdate () { + this.lValue = this.value + }, + computed: { + present () { + return typeof this.lValue !== 'undefined' + }, + dValue () { + return this.lValue || this.fallback || {} + }, + family: { + get () { + return this.dValue.family + }, + set (v) { + set(this.lValue, 'family', v) + this.$emit('input', this.lValue) + } + }, + isCustom () { + return this.preset === 'custom' + }, + preset: { + get () { + if (this.family === 'serif' || + this.family === 'sans-serif' || + this.family === 'monospace' || + this.family === 'inherit') { + return this.family + } else { + return 'custom' + } + }, + set (v) { + this.family = v === 'custom' ? '' : v + } + } + } +} diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue @@ -0,0 +1,54 @@ +<template> +<div class="font-control style-control" :class="{ custom: isCustom }"> + <label :for="preset === 'custom' ? name : name + '-font-switcher'" class="label"> + {{label}} + </label> + <input + v-if="typeof fallback !== 'undefined'" + class="opt exlcude-disabled" + type="checkbox" + :id="name + '-o'" + :checked="present" + @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"> + <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> + <label :for="name + '-font-switcher'" class="select" :disabled="!present"> + <select + :disabled="!present" + v-model="preset" + class="font-switcher" + :id="name + '-font-switcher'"> + <option v-for="option in availableOptions" :value="option"> + {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }} + </option> + </select> + <i class="icon-down-open"/> + </label> + <input + v-if="isCustom" + class="custom-font" + type="text" + :id="name" + v-model="family"> +</div> +</template> + +<script src="./font_control.js" ></script> + +<style lang="scss"> +@import '../../_variables.scss'; +.font-control { + input.custom-font { + min-width: 10em; + } + &.custom { + .select { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .custom-font { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } +} +</style> diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js @@ -6,7 +6,8 @@ import { highlightClass, highlightStyle } from '../../services/user_highlighter/ const Notification = { data () { return { - userExpanded: false + userExpanded: false, + betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, props: [ diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue @@ -2,7 +2,7 @@ <status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status> <div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]"v-else> <a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> - <StillImage class='avatar-compact' :src="notification.action.user.profile_image_url_original"/> + <StillImage class='avatar-compact' :class="{'better-shadow': betterShadow}" :src="notification.action.user.profile_image_url_original"/> </a> <div class='notification-right'> <div class="usercard notification-usercard" v-if="userExpanded"> diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss @@ -4,31 +4,28 @@ // a bit of a hack to allow scrolling below notifications padding-bottom: 15em; - .unseen-count { - display: inline-block; - background-color: $fallback--cRed; - background-color: var(--cRed, $fallback--cRed); - text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5); - border-radius: 99px; - min-width: 22px; - max-width: 22px; - min-height: 22px; - max-height: 22px; - color: white; - font-size: 15px; - line-height: 22px; - text-align: center; - vertical-align: middle - } - .loadmore-error { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } - .unseen { - box-shadow: inset 4px 0 0 var(--cRed, $fallback--cRed); - padding-left: 0; + .notification { + position: relative; + + .notification-overlay { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + pointer-events: none; + } + + &.unseen { + .notification-overlay { + background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px) + } + } } } @@ -42,21 +39,27 @@ .broken-favorite { border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - color: $fallback--faint; - color: var(--faint, $fallback--faint); - background-color: $fallback--cAlertRed; - background-color: var(--cAlertRed, $fallback--cAlertRed); + color: $fallback--text; + color: var(--alertErrorText, $fallback--text); + background-color: $fallback--alertError; + background-color: var(--alertError, $fallback--alertError); padding: 2px .5em } .avatar-compact { width: 32px; height: 32px; + box-shadow: var(--avatarStatusShadow); border-radius: $fallback--avatarAltRadius; border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); overflow: hidden; line-height: 0; + &.better-shadow { + box-shadow: var(--avatarStatusShadowInset); + filter: var(--avatarStatusShadowFilter) + } + &.animated::before { display: none; } @@ -90,6 +93,9 @@ padding: 0.25em 0; color: $fallback--faint; color: var(--faint, $fallback--faint); + a { + color: var(--faintLink); + } } padding: 0; .media-body { diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue @@ -4,7 +4,7 @@ <div class="panel-heading"> <div class="title"> {{$t('notifications.notifications')}} - <span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span> + <span class="badge badge-notification unseen-count" v-if="unseenCount">{{unseenCount}}</span> </div> <div @click.prevent class="loadmore-error alert error" v-if="error"> {{$t('timeline.error_fetching')}} @@ -13,6 +13,7 @@ </div> <div class="panel-body"> <div v-for="notification in visibleNotifications" :key="notification.action.id" class="notification" :class='{"unseen": !notification.seen}'> + <div class="notification-overlay"></div> <notification :notification="notification"></notification> </div> </div> diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue @@ -0,0 +1,38 @@ +<template> +<div class="opacity-control style-control" :class="{ disabled: !present || disabled }"> + <label :for="name" class="label"> + {{$t('settings.style.common.opacity')}} + </label> + <input + v-if="typeof fallback !== 'undefined'" + class="opt exclude-disabled" + :id="name + '-o'" + type="checkbox" + :checked="present" + @input="$emit('input', !present ? fallback : undefined)"> + <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> + <input + :id="name" + class="input-number" + type="number" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + max="1" + min="0" + step=".05"> +</div> +</template> + +<script> +export default { + props: [ + 'name', 'value', 'fallback', 'disabled' + ], + computed: { + present () { + return typeof this.value !== 'undefined' + } + } +} +</script> diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue @@ -153,8 +153,8 @@ padding-bottom: 0; margin-left: $fallback--attachmentRadius; margin-left: var(--attachmentRadius, $fallback--attachmentRadius); - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: $fallback--fg; + background-color: var(--btn, $fallback--fg); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } @@ -258,11 +258,13 @@ position: absolute; z-index: 1; box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); + // this doesn't match original but i don't care, making it uniform. + box-shadow: var(--popupShadow); min-width: 75%; background: $fallback--bg; background: var(--bg, $fallback--bg); - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); } .autocomplete { @@ -291,8 +293,8 @@ } &.highlighted { - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: $fallback--fg; + background-color: var(--lightBg, $fallback--fg); } } } diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue @@ -0,0 +1,48 @@ +<template> +<div class="range-control style-control" :class="{ disabled: !present || disabled }"> + <label :for="name" class="label"> + {{label}} + </label> + <input + v-if="typeof fallback !== 'undefined'" + class="opt exclude-disabled" + :id="name + '-o'" + type="checkbox" + :checked="present" + @input="$emit('input', !present ? fallback : undefined)"> + <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> + <input + :id="name" + class="input-number" + type="range" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + :max="max || hardMax || 100" + :min="min || hardMin || 0" + :step="step || 1"> + <input + :id="name" + class="input-number" + type="number" + :value="value || fallback" + :disabled="!present || disabled" + @input="$emit('input', $event.target.value)" + :max="hardMax" + :min="hardMin" + :step="step || 1"> +</div> +</template> + +<script> +export default { + props: [ + 'name', 'value', 'fallback', 'disabled', 'label', 'max', 'min', 'step', 'hardMin', 'hardMax' + ], + computed: { + present () { + return typeof this.value !== 'undefined' + } + } +} +</script> diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue @@ -18,6 +18,7 @@ </transition> </div> <div class="panel-body"> +<keep-alive> <tab-switcher> <div :label="$t('settings.general')" > <div class="setting-item"> @@ -207,6 +208,7 @@ </div> </tab-switcher> +</keep-alive> </div> </div> </template> @@ -218,7 +220,7 @@ @import '../../_variables.scss'; .setting-item { - border-bottom: 2px solid var(--btn, $fallback--btn); + border-bottom: 2px solid var(--fg, $fallback--fg); margin: 1em 1em 1.4em; padding-bottom: 1.4em; @@ -267,12 +269,8 @@ .btn { min-height: 28px; - } - - .submit { - margin-top: 1em; - min-height: 30px; - width: 10em; + min-width: 10em; + padding: 0 2em; } } .select-multiple { diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js @@ -0,0 +1,87 @@ +import ColorInput from '../color_input/color_input.vue' +import OpacityInput from '../opacity_input/opacity_input.vue' +import { getCssShadow } from '../../services/style_setter/style_setter.js' +import { hex2rgb } from '../../services/color_convert/color_convert.js' + +export default { + // 'Value' and 'Fallback' can be undefined, but if they are + // initially vue won't detect it when they become something else + // therefore i'm using "ready" which should be passed as true when + // data becomes available + props: [ + 'value', 'fallback', 'ready' + ], + data () { + return { + selectedId: 0, + // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) + cValue: this.value || this.fallback || [] + } + }, + components: { + ColorInput, + OpacityInput + }, + methods: { + add () { + this.cValue.push(Object.assign({}, this.selected)) + this.selectedId = this.cValue.length - 1 + }, + del () { + this.cValue.splice(this.selectedId, 1) + this.selectedId = this.cValue.length === 0 ? undefined : this.selectedId - 1 + }, + moveUp () { + const movable = this.cValue.splice(this.selectedId, 1)[0] + this.cValue.splice(this.selectedId - 1, 0, movable) + this.selectedId -= 1 + }, + moveDn () { + const movable = this.cValue.splice(this.selectedId, 1)[0] + this.cValue.splice(this.selectedId + 1, 0, movable) + this.selectedId += 1 + } + }, + beforeUpdate () { + this.cValue = this.value || this.fallback + }, + computed: { + selected () { + if (this.ready && this.cValue.length > 0) { + return this.cValue[this.selectedId] + } else { + return { + x: 0, + y: 0, + blur: 0, + spread: 0, + inset: false, + color: '#000000', + alpha: 1 + } + } + }, + moveUpValid () { + return this.ready && this.selectedId > 0 + }, + moveDnValid () { + return this.ready && this.selectedId < this.cValue.length - 1 + }, + present () { + return this.ready && + typeof this.cValue[this.selectedId] !== 'undefined' && + !this.usingFallback + }, + usingFallback () { + return typeof this.value === 'undefined' + }, + rgb () { + return hex2rgb(this.selected.color) + }, + style () { + return this.ready ? { + boxShadow: getCssShadow(this.cValue) + } : {} + } + } +} diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue @@ -0,0 +1,243 @@ +<template> +<div class="shadow-control" :class="{ disabled: !present }"> + <div class="shadow-preview-container"> + <div :disabled="!present" class="y-shift-control"> + <input + v-model="selected.y" + :disabled="!present" + class="input-number" + type="number"> + <div class="wrap"> + <input + v-model="selected.y" + :disabled="!present" + class="input-range" + type="range" + max="20" + min="-20"> + </div> + </div> + <div class="preview-window"> + <div class="preview-block" :style="style"></div> + </div> + <div :disabled="!present" class="x-shift-control"> + <input + v-model="selected.x" + :disabled="!present" + class="input-number" + type="number"> + <div class="wrap"> + <input + v-model="selected.x" + :disabled="!present" + class="input-range" + type="range" + max="20" + min="-20"> + </div> + </div> + </div> + + <div class="shadow-tweak"> + <div :disabled="usingFallback" class="id-control style-control"> + <label for="shadow-switcher" class="select" :disabled="!ready || usingFallback"> + <select + v-model="selectedId" class="shadow-switcher" + :disabled="!ready || usingFallback" + id="shadow-switcher"> + <option v-for="(shadow, index) in cValue" :value="index"> + {{$t('settings.style.shadows.shadow_id', { value: index })}} + </option> + </select> + <i class="icon-down-open"/> + </label> + <button class="btn btn-default" :disabled="!ready || !present" @click="del"> + <i class="icon-cancel"/> + </button> + <button class="btn btn-default" :disabled="!moveUpValid" @click="moveUp"> + <i class="icon-up-open"/> + </button> + <button class="btn btn-default" :disabled="!moveDnValid" @click="moveDn"> + <i class="icon-down-open"/> + </button> + <button class="btn btn-default" :disabled="usingFallback" @click="add"> + <i class="icon-plus"/> + </button> + </div> + <div :disabled="!present" class="inset-control style-control"> + <label for="inset" class="label"> + {{$t('settings.style.shadows.inset')}} + </label> + <input + v-model="selected.inset" + :disabled="!present" + name="inset" + id="inset" + class="input-inset" + type="checkbox"> + <label class="checkbox-label" for="inset"></label> + </div> + <div :disabled="!present" class="blur-control style-control"> + <label for="spread" class="label"> + {{$t('settings.style.shadows.blur')}} + </label> + <input + v-model="selected.blur" + :disabled="!present" + name="blur" + id="blur" + class="input-range" + type="range" + max="20" + min="0"> + <input + v-model="selected.blur" + :disabled="!present" + class="input-number" + type="number" + min="0"> + </div> + <div :disabled="!present" class="spread-control style-control"> + <label for="spread" class="label"> + {{$t('settings.style.shadows.spread')}} + </label> + <input + v-model="selected.spread" + :disabled="!present" + name="spread" + id="spread" + class="input-range" + type="range" + max="20" + min="-20"> + <input + v-model="selected.spread" + :disabled="!present" + class="input-number" + type="number"> + </div> + <ColorInput + v-model="selected.color" + :disabled="!present" + :label="$t('settings.style.common.color')" + name="shadow"/> + <OpacityInput + v-model="selected.alpha" + :disabled="!present"/> + <p> + {{$t('settings.style.shadows.hint')}} + </p> + </div> +</div> +</template> + +<script src="./shadow_control.js" ></script> + +<style lang="scss"> +@import '../../_variables.scss'; +.shadow-control { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 1em; + + .shadow-preview-container, + .shadow-tweak { + margin: 5px 6px 0 0; + } + .shadow-preview-container { + flex: 0; + display: flex; + flex-wrap: wrap; + + $side: 15em; + + input[type=number] { + width: 5em; + min-width: 2em; + } + .x-shift-control, + .y-shift-control { + display: flex; + flex: 0; + + &[disabled=disabled] *{ + opacity: .5 + } + + } + + .x-shift-control { + align-items: flex-start; + } + + .x-shift-control .wrap, + input[type=range] { + margin: 0; + width: $side; + height: 2em; + } + .y-shift-control { + flex-direction: column; + align-items: flex-end; + .wrap { + width: 2em; + height: $side; + } + input[type=range] { + transform-origin: 1em 1em; + transform: rotate(90deg); + } + } + .preview-window { + flex: 1; + background-color: #999999; + display: flex; + align-items: center; + justify-content: center; + background-image: + linear-gradient(45deg, #666666 25%, transparent 25%), + linear-gradient(-45deg, #666666 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #666666 75%), + linear-gradient(-45deg, transparent 75%, #666666 75%); + background-size: 20px 20px; + background-position:0 0, 0 10px, 10px -10px, -10px 0; + + border-radius: $fallback--inputRadius; + border-radius: var(--inputRadius, $fallback--inputRadius); + + .preview-block { + width: 33%; + height: 33%; + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + } + } + } + + .shadow-tweak { + flex: 1; + min-width: 280px; + + .id-control { + align-items: stretch; + .select, .btn { + min-width: 1px; + margin-right: 5px; + } + .btn { + padding: 0 .4em; + margin: 0 .1em; + } + .select { + flex: 1; + select { + align-self: initial; + } + } + } + } +} +</style> diff --git a/src/components/status/status.js b/src/components/status/status.js @@ -33,7 +33,8 @@ const Status = { showingTall: false, expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined' ? !this.$store.state.instance.collapseMessageWithSubject - : !this.$store.state.config.collapseMessageWithSubject + : !this.$store.state.config.collapseMessageWithSubject, + betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, computed: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue @@ -9,7 +9,7 @@ </template> <template v-else> <div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info"> - <StillImage v-if="retweet" class='avatar' :src="statusoid.user.profile_image_url_original"/> + <StillImage v-if="retweet" class='avatar' :class='{ "better-shadow": betterShadow }' :src="statusoid.user.profile_image_url_original"/> <div class="media-body faint"> <a v-if="retweeterHtml" :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name" v-html="retweeterHtml"></a> <a v-else :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a> @@ -21,7 +21,7 @@ <div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet }]" :style="[ userStyle ]" class="media status"> <div v-if="!noHeading" class="media-left"> <a :href="status.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> - <StillImage class='avatar' :class="{'avatar-compact': compact}" :src="status.user.profile_image_url_original"/> + <StillImage class='avatar' :class="{'avatar-compact': compact, 'better-shadow': betterShadow}" :src="status.user.profile_image_url_original"/> </a> </div> <div class="status-body"> @@ -146,6 +146,7 @@ border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); + box-shadow: var(--popupShadow); margin-top: 0.25em; margin-left: 0.5em; z-index: 50; @@ -284,8 +285,8 @@ margin-left: 0.2em; } a:hover i { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } } @@ -323,6 +324,8 @@ .status-content { margin-right: 0.5em; + font-family: var(--postFont, sans-serif); + img, video { max-width: 100%; max-height: 400px; @@ -339,6 +342,10 @@ overflow: auto; } + code, samp, kbd, var, pre { + font-family: var(--postCodeFont, monospace); + } + p { margin: 0; margin-top: 0.2em; @@ -457,18 +464,30 @@ .status .avatar-compact { width: 32px; height: 32px; + box-shadow: var(--avatarStatusShadow); border-radius: $fallback--avatarAltRadius; border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + + &.better-shadow { + box-shadow: var(--avatarStatusShadowInset); + filter: var(--avatarStatusShadowFilter) + } } .avatar { width: 48px; height: 48px; + box-shadow: var(--avatarStatusShadow); border-radius: $fallback--avatarRadius; border-radius: var(--avatarRadius, $fallback--avatarRadius); overflow: hidden; position: relative; + &.better-shadow { + box-shadow: var(--avatarStatusShadowInset); + filter: var(--avatarStatusShadowFilter) + } + img { width: 100%; height: 100%; @@ -532,6 +551,7 @@ a.unmute { .status-el:last-child { border-bottom-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + border-bottom: none; } } diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue @@ -0,0 +1,78 @@ +<template> +<div class="panel dummy"> + <div class="panel-heading"> + <div class="title"> + {{$t('settings.style.preview.header')}} + <span class="badge badge-notification"> + 99 + </span> + </div> + <span class="faint"> + {{$t('settings.style.preview.header_faint')}} + </span> + <span class="alert error"> + {{$t('settings.style.preview.error')}} + </span> + <button class="btn"> + {{$t('settings.style.preview.button')}} + </button> + </div> + <div class="panel-body theme-preview-content"> + <div class="post"> + <div class="avatar"> + ( ͡° ͜ʖ ͡°) + </div> + <div class="content"> + <h4> + {{$t('settings.style.preview.content')}} + </h4> + + <i18n path="settings.style.preview.text"> + <code style="font-family: var(--postCodeFont)"> + {{$t('settings.style.preview.mono')}} + </code> + <a style="color: var(--link)"> + {{$t('settings.style.preview.link')}} + </a> + </i18n> + + <div class="icons"> + <i style="color: var(--cBlue)" class="icon-reply"/> + <i style="color: var(--cGreen)" class="icon-retweet"/> + <i style="color: var(--cOrange)" class="icon-star"/> + <i style="color: var(--cRed)" class="icon-cancel"/> + </div> + </div> + </div> + + <div class="after-post"> + <div class="avatar-alt"> + :^) + </div> + <div class="content"> + <i18n path="settings.style.preview.fine_print" tag="span" class="faint"> + <a style="color: var(--faintLink)"> + {{$t('settings.style.preview.faint_link')}} + </a> + </i18n> + </div> + </div> + <div class="separator"></div> + + <span class="alert error"> + {{$t('settings.style.preview.error')}} + </span> + <input :value="$t('settings.style.preview.input')" type="text"> + + <div class="actions"> + <span class="checkbox"> + <input checked="very yes" type="checkbox" id="preview_checkbox"> + <label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label> + </span> + <button class="btn"> + {{$t('settings.style.preview.button')}} + </button> + </div> + </div> +</div> +</template> diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js @@ -1,21 +1,101 @@ -import { rgbstr2hex } from '../../services/color_convert/color_convert.js' +import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js' +import { set, delete as del } from 'vue' +import { generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } 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' +import ShadowControl from '../shadow_control/shadow_control.vue' +import FontControl from '../font_control/font_control.vue' +import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' +import TabSwitcher from '../tab_switcher/tab_switcher.jsx' +import Preview from './preview.vue' +import ExportImport from '../export_import/export_import.vue' + +// List of color values used in v1 +const v1OnlyNames = [ + 'bg', + 'fg', + 'text', + 'link', + 'cRed', + 'cGreen', + 'cBlue', + 'cOrange' +].map(_ => _ + 'ColorLocal') export default { data () { return { availableStyles: [], selected: this.$store.state.config.theme, - invalidThemeImported: false, - bgColorLocal: '', - btnColorLocal: '', + + previewShadows: {}, + previewColors: {}, + previewRadii: {}, + previewFonts: {}, + + shadowsInvalid: true, + colorsInvalid: true, + radiiInvalid: true, + + keepColor: false, + keepShadows: false, + keepOpacity: false, + keepRoundness: false, + keepFonts: false, + textColorLocal: '', linkColorLocal: '', - redColorLocal: '', - blueColorLocal: '', - greenColorLocal: '', - orangeColorLocal: '', + + bgColorLocal: '', + bgOpacityLocal: undefined, + + fgColorLocal: '', + fgTextColorLocal: undefined, + fgLinkColorLocal: undefined, + + btnColorLocal: undefined, + btnTextColorLocal: undefined, + btnOpacityLocal: undefined, + + inputColorLocal: undefined, + inputTextColorLocal: undefined, + inputOpacityLocal: undefined, + + panelColorLocal: undefined, + panelTextColorLocal: undefined, + panelLinkColorLocal: undefined, + panelFaintColorLocal: undefined, + panelOpacityLocal: undefined, + + topBarColorLocal: undefined, + topBarTextColorLocal: undefined, + topBarLinkColorLocal: undefined, + + alertErrorColorLocal: undefined, + + badgeOpacityLocal: undefined, + badgeNotificationColorLocal: undefined, + + borderColorLocal: undefined, + borderOpacityLocal: undefined, + + faintColorLocal: undefined, + faintOpacityLocal: undefined, + faintLinkColorLocal: undefined, + + cRedColorLocal: '', + cBlueColorLocal: '', + cGreenColorLocal: '', + cOrangeColorLocal: '', + + shadowSelected: undefined, + shadowsLocal: {}, + fontsLocal: {}, + btnRadiusLocal: '', inputRadiusLocal: '', + checkboxRadiusLocal: '', panelRadiusLocal: '', avatarRadiusLocal: '', avatarAltRadiusLocal: '', @@ -26,144 +106,470 @@ export default { created () { const self = this - window.fetch('/static/styles.json') - .then((data) => data.json()) - .then((themes) => { - self.availableStyles = themes - }) + getThemes().then((themesComplete) => { + self.availableStyles = themesComplete + }) }, mounted () { - this.normalizeLocalState(this.$store.state.config.colors, this.$store.state.config.radii) + this.normalizeLocalState(this.$store.state.config.customTheme) + if (typeof this.shadowSelected === 'undefined') { + this.shadowSelected = this.shadowsAvailable[0] + } }, - methods: { - exportCurrentTheme () { - const stringified = JSON.stringify({ + computed: { + selectedVersion () { + return Array.isArray(this.selected) ? 1 : 2 + }, + currentColors () { + return { + bg: this.bgColorLocal, + text: this.textColorLocal, + link: this.linkColorLocal, + + fg: this.fgColorLocal, + fgText: this.fgTextColorLocal, + fgLink: this.fgLinkColorLocal, + + panel: this.panelColorLocal, + panelText: this.panelTextColorLocal, + panelLink: this.panelLinkColorLocal, + panelFaint: this.panelFaintColorLocal, + + input: this.inputColorLocal, + inputText: this.inputTextColorLocal, + + topBar: this.topBarColorLocal, + topBarText: this.topBarTextColorLocal, + topBarLink: this.topBarLinkColorLocal, + + btn: this.btnColorLocal, + btnText: this.btnTextColorLocal, + + alertError: this.alertErrorColorLocal, + badgeNotification: this.badgeNotificationColorLocal, + + faint: this.faintColorLocal, + faintLink: this.faintLinkColorLocal, + border: this.borderColorLocal, + + cRed: this.cRedColorLocal, + cBlue: this.cBlueColorLocal, + cGreen: this.cGreenColorLocal, + cOrange: this.cOrangeColorLocal + } + }, + currentOpacity () { + return { + bg: this.bgOpacityLocal, + btn: this.btnOpacityLocal, + input: this.inputOpacityLocal, + panel: this.panelOpacityLocal, + topBar: this.topBarOpacityLocal, + border: this.borderOpacityLocal, + faint: this.faintOpacityLocal + } + }, + currentRadii () { + return { + btn: this.btnRadiusLocal, + input: this.inputRadiusLocal, + checkbox: this.checkboxRadiusLocal, + panel: this.panelRadiusLocal, + avatar: this.avatarRadiusLocal, + avatarAlt: this.avatarAltRadiusLocal, + tooltip: this.tooltipRadiusLocal, + attachment: this.attachmentRadiusLocal + } + }, + preview () { + return composePreset(this.previewColors, this.previewRadii, this.previewShadows, this.previewFonts) + }, + previewTheme () { + if (!this.preview.theme.colors) return { colors: {}, opacity: {}, radii: {}, shadows: {}, fonts: {} } + return this.preview.theme + }, + // This needs optimization maybe + previewContrast () { + if (!this.previewTheme.colors.bg) return {} + const colors = this.previewTheme.colors + const opacity = this.previewTheme.opacity + if (!colors.bg) return {} + const hints = (ratio) => ({ + text: ratio.toPrecision(3) + ':1', + // AA level, AAA level + aa: ratio >= 4.5, + aaa: ratio >= 7, + // same but for 18pt+ texts + laa: ratio >= 3, + laaa: ratio >= 4.5 + }) + + // fgsfds :DDDD + const fgs = { + text: hex2rgb(colors.text), + panelText: hex2rgb(colors.panelText), + panelLink: hex2rgb(colors.panelLink), + btnText: hex2rgb(colors.btnText), + topBarText: hex2rgb(colors.topBarText), + inputText: hex2rgb(colors.inputText), + + link: hex2rgb(colors.link), + topBarLink: hex2rgb(colors.topBarLink), + + red: hex2rgb(colors.cRed), + green: hex2rgb(colors.cGreen), + blue: hex2rgb(colors.cBlue), + orange: hex2rgb(colors.cOrange) + } + + const bgs = { + bg: hex2rgb(colors.bg), + btn: hex2rgb(colors.btn), + panel: hex2rgb(colors.panel), + topBar: hex2rgb(colors.topBar), + input: hex2rgb(colors.input), + alertError: hex2rgb(colors.alertError), + badgeNotification: hex2rgb(colors.badgeNotification) + } + + /* This is a bit confusing because "bottom layer" used is text color + * This is done to get worst case scenario when background below transparent + * layer matches text color, making it harder to read the lower alpha is. + */ + const ratios = { + bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text), + bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link), + bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red), + bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green), + bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue), + bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange), + + tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text), + + panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText), + panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink), + + btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText), + + inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText), + + topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText), + topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink) + } + + return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {}) + }, + previewRules () { + if (!this.preview.rules) return '' + return [ + ...Object.values(this.preview.rules), + 'color: var(--text)', + 'font-family: var(--interfaceFont, sans-serif)' + ].join(';') + }, + shadowsAvailable () { + return Object.keys(this.previewTheme.shadows).sort() + }, + currentShadowOverriden: { + get () { + return !!this.currentShadow + }, + set (val) { + if (val) { + set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _))) + } else { + del(this.shadowsLocal, this.shadowSelected) + } + } + }, + currentShadowFallback () { + return this.previewTheme.shadows[this.shadowSelected] + }, + currentShadow: { + get () { + return this.shadowsLocal[this.shadowSelected] + }, + set (v) { + set(this.shadowsLocal, this.shadowSelected, v) + } + }, + themeValid () { + return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid + }, + exportedTheme () { + const saveEverything = ( + !this.keepFonts && + !this.keepShadows && + !this.keepOpacity && + !this.keepRoundness && + !this.keepColor + ) + + const theme = {} + + if (this.keepFonts || saveEverything) { + theme.fonts = this.fontsLocal + } + if (this.keepShadows || saveEverything) { + theme.shadows = this.shadowsLocal + } + if (this.keepOpacity || saveEverything) { + theme.opacity = this.currentOpacity + } + if (this.keepColor || saveEverything) { + theme.colors = this.currentColors + } + if (this.keepRoundness || saveEverything) { + theme.radii = this.currentRadii + } + + return { // To separate from other random JSON files and possible future theme formats - _pleroma_theme_version: 1, - colors: this.$store.state.config.colors, - radii: this.$store.state.config.radii - }, null, 2) // Pretty-print and indent with 2 spaces - - // Create an invisible link with a data url and simulate a click - const e = document.createElement('a') - e.setAttribute('download', 'pleroma_theme.json') - e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) - e.style.display = 'none' - - document.body.appendChild(e) - e.click() - document.body.removeChild(e) - }, - - importTheme () { - this.invalidThemeImported = false - const filePicker = document.createElement('input') - filePicker.setAttribute('type', 'file') - filePicker.setAttribute('accept', '.json') - - filePicker.addEventListener('change', event => { - if (event.target.files[0]) { - // eslint-disable-next-line no-undef - const reader = new FileReader() - reader.onload = ({target}) => { - try { - const parsed = JSON.parse(target.result) - if (parsed._pleroma_theme_version === 1) { - this.normalizeLocalState(parsed.colors, parsed.radii) - } else { - // A theme from the future, spooky - this.invalidThemeImported = true - } - } catch (e) { - // This will happen both if there is a JSON syntax error or the theme is missing components - this.invalidThemeImported = true - } - } - reader.readAsText(event.target.files[0]) + _pleroma_theme_version: 2, theme + } + } + }, + components: { + ColorInput, + OpacityInput, + RangeInput, + ContrastRatio, + ShadowControl, + FontControl, + TabSwitcher, + Preview, + ExportImport + }, + methods: { + setCustomTheme () { + this.$store.dispatch('setOption', { + name: 'customTheme', + value: { + shadows: this.shadowsLocal, + fonts: this.fontsLocal, + opacity: this.currentOpacity, + colors: this.currentColors, + radii: this.currentRadii } }) + }, + onImport (parsed) { + if (parsed._pleroma_theme_version === 1) { + this.normalizeLocalState(parsed, 1) + } else if (parsed._pleroma_theme_version === 2) { + this.normalizeLocalState(parsed.theme, 2) + } + }, + importValidator (parsed) { + const version = parsed._pleroma_theme_version + return version >= 1 || version <= 2 + }, + clearAll () { + const state = this.$store.state.config.customTheme + const version = state.colors ? 2 : 'l1' + this.normalizeLocalState(this.$store.state.config.customTheme, version) + }, - document.body.appendChild(filePicker) - filePicker.click() - document.body.removeChild(filePicker) + // Clears all the extra stuff when loading V1 theme + clearV1 () { + Object.keys(this.$data) + .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal')) + .filter(_ => !v1OnlyNames.includes(_)) + .forEach(key => { + set(this.$data, key, undefined) + }) }, - setCustomTheme () { - if (!this.bgColorLocal && !this.btnColorLocal && !this.linkColorLocal) { - // reset to picked themes - } - - const rgb = (hex) => { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null - } - const bgRgb = rgb(this.bgColorLocal) - const btnRgb = rgb(this.btnColorLocal) - const textRgb = rgb(this.textColorLocal) - const linkRgb = rgb(this.linkColorLocal) - - const redRgb = rgb(this.redColorLocal) - const blueRgb = rgb(this.blueColorLocal) - const greenRgb = rgb(this.greenColorLocal) - const orangeRgb = rgb(this.orangeColorLocal) - - if (bgRgb && btnRgb && linkRgb) { - this.$store.dispatch('setOption', { - name: 'customTheme', - value: { - fg: btnRgb, - bg: bgRgb, - text: textRgb, - link: linkRgb, - cRed: redRgb, - cBlue: blueRgb, - cGreen: greenRgb, - cOrange: orangeRgb, - btnRadius: this.btnRadiusLocal, - inputRadius: this.inputRadiusLocal, - panelRadius: this.panelRadiusLocal, - avatarRadius: this.avatarRadiusLocal, - avatarAltRadius: this.avatarAltRadiusLocal, - tooltipRadius: this.tooltipRadiusLocal, - attachmentRadius: this.attachmentRadiusLocal - }}) - } - }, - - normalizeLocalState (colors, radii) { - this.bgColorLocal = rgbstr2hex(colors.bg) - this.btnColorLocal = rgbstr2hex(colors.btn) - this.textColorLocal = rgbstr2hex(colors.fg) - this.linkColorLocal = rgbstr2hex(colors.link) - - this.redColorLocal = rgbstr2hex(colors.cRed) - this.blueColorLocal = rgbstr2hex(colors.cBlue) - this.greenColorLocal = rgbstr2hex(colors.cGreen) - this.orangeColorLocal = rgbstr2hex(colors.cOrange) - - this.btnRadiusLocal = radii.btnRadius || 4 - this.inputRadiusLocal = radii.inputRadius || 4 - this.panelRadiusLocal = radii.panelRadius || 10 - this.avatarRadiusLocal = radii.avatarRadius || 5 - this.avatarAltRadiusLocal = radii.avatarAltRadius || 50 - this.tooltipRadiusLocal = radii.tooltipRadius || 2 - this.attachmentRadiusLocal = radii.attachmentRadius || 5 + clearRoundness () { + Object.keys(this.$data) + .filter(_ => _.endsWith('RadiusLocal')) + .forEach(key => { + set(this.$data, key, undefined) + }) + }, + + clearOpacity () { + Object.keys(this.$data) + .filter(_ => _.endsWith('OpacityLocal')) + .forEach(key => { + set(this.$data, key, undefined) + }) + }, + + clearShadows () { + this.shadowsLocal = {} + }, + + clearFonts () { + this.fontsLocal = {} + }, + + /** + * This applies stored theme data onto form. Supports three versions of data: + * 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 {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type + */ + normalizeLocalState (input, version = 0) { + const colors = input.colors || input + const radii = input.radii || input + const opacity = input.opacity + const shadows = input.shadows || {} + const fonts = input.fonts || {} + + if (version === 0) { + if (input.version) version = input.version + // Old v1 naming: fg is text, btn is foreground + if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') { + version = 1 + } + // New v2 naming: text is text, fg is foreground + if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') { + version = 2 + } + } + + // Stuff that differs between V1 and V2 + if (version === 1) { + this.fgColorLocal = rgb2hex(colors.btn) + this.textColorLocal = rgb2hex(colors.fg) + } + + if (!this.keepColor) { + this.clearV1() + const keys = new Set(version !== 1 ? Object.keys(colors) : []) + if (version === 1 || version === 'l1') { + keys + .add('bg') + .add('link') + .add('cRed') + .add('cBlue') + .add('cGreen') + .add('cOrange') + } + + keys.forEach(key => { + this[key + 'ColorLocal'] = rgb2hex(colors[key]) + }) + } + + if (!this.keepRoundness) { + this.clearRoundness() + Object.entries(radii).forEach(([k, v]) => { + // 'Radius' is kept mostly for v1->v2 localstorage transition + const key = k.endsWith('Radius') ? k.split('Radius')[0] : k + this[key + 'RadiusLocal'] = v + }) + } + + if (!this.keepShadows) { + this.clearShadows() + this.shadowsLocal = shadows + this.shadowSelected = this.shadowsAvailable[0] + } + + if (!this.keepFonts) { + this.clearFonts() + this.fontsLocal = fonts + } + + if (opacity && !this.keepOpacity) { + this.clearOpacity() + Object.entries(opacity).forEach(([k, v]) => { + if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return + this[k + 'OpacityLocal'] = v + }) + } } }, watch: { + currentRadii () { + try { + this.previewRadii = generateRadii({ radii: this.currentRadii }) + this.radiiInvalid = false + } catch (e) { + this.radiiInvalid = true + console.warn(e) + } + }, + shadowsLocal: { + handler () { + try { + this.previewShadows = generateShadows({ shadows: this.shadowsLocal }) + this.shadowsInvalid = false + } catch (e) { + this.shadowsInvalid = true + console.warn(e) + } + }, + deep: true + }, + fontsLocal: { + handler () { + try { + this.previewFonts = generateFonts({ fonts: this.fontsLocal }) + this.fontsInvalid = false + } catch (e) { + this.fontsInvalid = true + console.warn(e) + } + }, + deep: true + }, + currentColors () { + try { + this.previewColors = generateColors({ + opacity: this.currentOpacity, + colors: this.currentColors + }) + this.colorsInvalid = false + } catch (e) { + this.colorsInvalid = true + console.warn(e) + } + }, + currentOpacity () { + try { + this.previewColors = generateColors({ + opacity: this.currentOpacity, + colors: this.currentColors + }) + } catch (e) { + console.warn(e) + } + }, selected () { - this.bgColorLocal = this.selected[1] - this.btnColorLocal = this.selected[2] - this.textColorLocal = this.selected[3] - this.linkColorLocal = this.selected[4] - this.redColorLocal = this.selected[5] - this.greenColorLocal = this.selected[6] - this.blueColorLocal = this.selected[7] - this.orangeColorLocal = this.selected[8] + if (this.selectedVersion === 1) { + if (!this.keepRoundness) { + this.clearRoundness() + } + + if (!this.keepShadows) { + this.clearShadows() + } + + if (!this.keepOpacity) { + this.clearOpacity() + } + + if (!this.keepColor) { + this.clearV1() + + this.bgColorLocal = this.selected[1] + this.fgColorLocal = this.selected[2] + this.textColorLocal = this.selected[3] + this.linkColorLocal = this.selected[4] + this.cRedColorLocal = this.selected[5] + this.cGreenColorLocal = this.selected[6] + this.cBlueColorLocal = this.selected[7] + this.cOrangeColorLocal = this.selected[8] + } + } else if (this.selectedVersion >= 2) { + this.normalizeLocalState(this.selected.theme, 2) + } } } } diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss @@ -0,0 +1,335 @@ +@import '../../_variables.scss'; +.style-switcher { + .preset-switcher { + margin-right: 1em; + } + + .style-control { + display: flex; + align-items: baseline; + margin-bottom: 5px; + + .label { + flex: 1; + } + + &.disabled { + input, select { + &:not(.exclude-disabled) { + opacity: .5 + } + } + } + + input, select { + min-width: 3em; + margin: 0; + flex: 0; + + &[type=color] { + padding: 1px; + cursor: pointer; + height: 29px; + min-width: 2em; + border: none; + align-self: stretch; + } + + &[type=number] { + min-width: 5em; + } + + &[type=range] { + flex: 1; + min-width: 3em; + } + + &[type=checkbox] + label { + margin: 6px 0; + } + + &:not([type=number]):not([type=text]) { + align-self: flex-start; + } + } + } + + .tab-switcher { + margin: 0 -1em; + } + + .reset-container { + flex-wrap: wrap; + } + + .fonts-container, + .reset-container, + .apply-container, + .radius-container, + .color-container, + { + display: flex; + } + + .fonts-container, + .radius-container { + flex-direction: column; + } + + .color-container{ + > h4 { + width: 99%; + } + flex-wrap: wrap; + justify-content: space-between; + } + + .fonts-container, + .color-container, + .shadow-container, + .radius-container, + .presets-container { + margin: 1em 1em 0; + } + + .tab-header { + display: flex; + justify-content: space-between; + align-items: baseline; + width: 100%; + min-height: 30px; + + .btn { + min-width: 1px; + flex: 0 auto; + padding: 0 1em; + } + + p { + flex: 1; + margin: 0; + margin-right: .5em; + } + + margin-bottom: 1em; + } + + .shadow-selector { + .override { + flex: 1; + margin-left: .5em; + } + .select-container { + margin-top: -4px; + margin-bottom: -3px; + } + } + + .save-load, + .save-load-options { + display: flex; + justify-content: center; + align-items: baseline; + flex-wrap: wrap; + + .presets, + .import-export { + margin-bottom: .5em; + } + + .import-export { + display: flex; + } + + .override { + margin-left: .5em; + } + } + + .save-load-options { + flex-wrap: wrap; + margin-top: .5em; + justify-content: center; + .keep-option { + margin: 0 .5em .5em; + min-width: 25%; + } + } + + .preview-container { + border-top: 1px dashed; + border-bottom: 1px dashed; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + margin: 1em -1em 0; + padding: 1em; + background: var(--body-background-image); + background-size: cover; + background-position: 50% 50%; + + .dummy { + .post { + font-family: var(--postFont); + display: flex; + + .content { + flex: 1; + + h4 { + margin-bottom: .25em; + } + + .icons { + margin-top: .5em; + display: flex; + + i { + margin-right: 1em; + } + } + } + } + + .after-post { + margin-top: 1em; + display: flex; + align-items: center; + } + + .avatar, .avatar-alt{ + background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%); + color: black; + font-family: sans-serif; + text-align: center; + margin-right: 1em; + } + + .avatar-alt { + flex: 0 auto; + margin-left: 28px; + font-size: 12px; + min-width: 20px; + min-height: 20px; + line-height: 20px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + } + + .avatar { + flex: 0 auto; + width: 48px; + height: 48px; + font-size: 14px; + line-height: 48px; + } + + .actions { + display: flex; + align-items: baseline; + + .checkbox { + display: inline-flex; + align-items: baseline; + margin-right: 1em; + flex: 1; + } + } + + .separator { + margin: 1em; + border-bottom: 1px solid; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + } + + .panel-heading { + .badge, .alert, .btn, .faint { + margin-left: 1em; + white-space: nowrap; + } + .faint { + text-overflow: ellipsis; + min-width: 2em; + overflow-x: hidden; + } + .flex-spacer { + flex: 1; + } + } + .btn { + margin-left: 0; + padding: 0 1em; + min-width: 3em; + min-height: 30px; + } + } + } + + .apply-container { + justify-content: center; + } + + .radius-item, + .color-item { + min-width: 20em; + margin: 5px 6px 0 0; + display:flex; + flex-direction: column; + flex: 1 1 0; + + &.wide { + min-width: 60% + } + + &:not(.wide):nth-child(2n+1) { + margin-right: 7px; + + } + + .color, .opacity { + display:flex; + align-items: baseline; + } + } + + .radius-item { + flex-basis: auto; + } + + .theme-radius-rn, + .theme-color-cl { + border: 0; + box-shadow: none; + background: transparent; + color: var(--faint, $fallback--faint); + align-self: stretch; + } + + .theme-color-cl, + .theme-radius-in, + .theme-color-in { + margin-left: 4px; + } + + .theme-radius-in { + min-width: 1em; + } + + .theme-radius-in { + max-width: 7em; + flex: 1; + } + + .theme-radius-lb{ + max-width: 50em; + } + + .theme-preview-content { + padding: 20px; + } + + .btn { + margin-left: .25em; + margin-right: .25em; + } +} diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue @@ -1,300 +1,276 @@ <template> -<div> +<div class="style-switcher"> <div class="presets-container"> - <div> - {{$t('settings.presets')}} - <label for="style-switcher" class='select'> - <select id="style-switcher" v-model="selected" class="style-switcher"> - <option v-for="style in availableStyles" - :value="style" - :style="{ - backgroundColor: style[1], - color: style[3] - }"> - {{style[0]}} - </option> - </select> - <i class="icon-down-open"/> - </label> + <div class="save-load"> + <export-import + :exportObject='exportedTheme' + :exportLabel='$t("settings.export_theme")' + :importLabel='$t("settings.import_theme")' + :importFailedText='$t("settings.invalid_theme_imported")' + :onImport='onImport' + :validator='importValidator'> + <template slot="before"> + <div class="presets"> + {{$t('settings.presets')}} + <label for="preset-switcher" class='select'> + <select id="preset-switcher" v-model="selected" class="preset-switcher"> + <option v-for="style in availableStyles" + :value="style" + :style="{ + backgroundColor: style[1] || style.theme.colors.bg, + color: style[3] || style.theme.colors.text + }"> + {{style[0] || style.name}} + </option> + </select> + <i class="icon-down-open"/> + </label> + </div> + </template> + </export-import> </div> - <div class="import-export"> - <button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button> - <button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button> - <p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p> + <div class="save-load-options"> + <span class="keep-option"> + <input + id="keep-color" + type="checkbox" + v-model="keepColor"> + <label for="keep-color">{{$t('settings.style.switcher.keep_color')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-shadows" + type="checkbox" + v-model="keepShadows"> + <label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-opacity" + type="checkbox" + v-model="keepOpacity"> + <label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-roundness" + type="checkbox" + v-model="keepRoundness"> + <label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-fonts" + type="checkbox" + v-model="keepFonts"> + <label for="keep-fonts">{{$t('settings.style.switcher.keep_fonts')}}</label> + </span> + <p>{{$t('settings.style.switcher.save_load_hint')}}</p> </div> </div> <div class="preview-container"> - <div :style="{ - '--btnRadius': btnRadiusLocal + 'px', - '--inputRadius': inputRadiusLocal + 'px', - '--panelRadius': panelRadiusLocal + 'px', - '--avatarRadius': avatarRadiusLocal + 'px', - '--avatarAltRadius': avatarAltRadiusLocal + 'px', - '--tooltipRadius': tooltipRadiusLocal + 'px', - '--attachmentRadius': attachmentRadiusLocal + 'px' - }"> - <div class="panel dummy"> - <div class="panel-heading" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Preview</div> - <div class="panel-body theme-preview-content" :style="{ 'background-color': bgColorLocal, 'color': textColorLocal }"> - <div class="avatar" :style="{ - 'border-radius': avatarRadiusLocal + 'px' - }"> - ( ͡° ͜ʖ ͡°) - </div> - <h4>Content</h4> - <br> - A bunch of more content and - <a :style="{ color: linkColorLocal }">a nice lil' link</a> - <i :style="{ color: blueColorLocal }" class="icon-reply"/> - <i :style="{ color: greenColorLocal }" class="icon-retweet"/> - <i :style="{ color: redColorLocal }" class="icon-cancel"/> - <i :style="{ color: orangeColorLocal }" class="icon-star"/> - <br> - <button class="btn" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Button</button> + <preview :style="previewRules"/> + </div> + + <keep-alive> + <tab-switcher key="style-tweak"> + <div :label="$t('settings.style.common_colors._tab_label')" class="color-container"> + <div class="tab-header"> + <p>{{$t('settings.theme_help')}}</p> + <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button> + <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button> + </div> + <p>{{$t('settings.theme_help_v2_1')}}</p> + <h4>{{ $t('settings.style.common_colors.main') }}</h4> + <div class="color-item"> + <ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/> + <OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/> + <ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/> + <ContrastRatio :contrast="previewContrast.bgText"/> + <ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/> + <ContrastRatio :contrast="previewContrast.bgLink"/> </div> + <div class="color-item"> + <ColorInput name="fgColor" v-model="fgColorLocal" :label="$t('settings.foreground')"/> + <ColorInput name="fgTextColor" v-model="fgTextColorLocal" :label="$t('settings.text')" :fallback="previewTheme.colors.fgText"/> + <ColorInput name="fgLinkColor" v-model="fgLinkColorLocal" :label="$t('settings.links')" :fallback="previewTheme.colors.fgLink"/> + <p>{{ $t('settings.style.common_colors.foreground_hint') }}</p> + </div> + <h4>{{ $t('settings.style.common_colors.rgbo') }}</h4> + <div class="color-item"> + <ColorInput name="cRedColor" v-model="cRedColorLocal" :label="$t('settings.cRed')"/> + <ContrastRatio :contrast="previewContrast.bgRed"/> + <ColorInput name="cBlueColor" v-model="cBlueColorLocal" :label="$t('settings.cBlue')"/> + <ContrastRatio :contrast="previewContrast.bgBlue"/> + </div> + <div class="color-item"> + <ColorInput name="cGreenColor" v-model="cGreenColorLocal" :label="$t('settings.cGreen')"/> + <ContrastRatio :contrast="previewContrast.bgGreen"/> + <ColorInput name="cOrangeColor" v-model="cOrangeColorLocal" :label="$t('settings.cOrange')"/> + <ContrastRatio :contrast="previewContrast.bgOrange"/> + </div> + <p>{{$t('settings.theme_help_v2_2')}}</p> </div> - </div> - </div> - <div class="color-container"> - <p>{{$t('settings.theme_help')}}</p> - <div class="color-item"> - <label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label> - <input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal"> - <input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal"> - </div> - <div class="color-item"> - <label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label> - <input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal"> - <input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal"> - </div> - <div class="color-item"> - <label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label> - <input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal"> - <input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal"> - </div> - <div class="color-item"> - <label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label> - <input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal"> - <input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal"> - </div> - <div class="color-item"> - <label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label> - <input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal"> - <input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal"> - </div> - <div class="color-item"> - <label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label> - <input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal"> - <input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal"> - </div> - <div class="color-item"> - <label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label> - <input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal"> - <input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal"> - </div> - <div class="color-item"> - <label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label> - <input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal"> - <input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal"> - </div> - </div> + <div :label="$t('settings.style.advanced_colors._tab_label')" class="color-container"> + <div class="tab-header"> + <p>{{$t('settings.theme_help')}}</p> + <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button> + <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4> + <ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError"/> + <ContrastRatio :contrast="previewContrast.alertError"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4> + <ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4> + <ColorInput name="panelColor" v-model="panelColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/> + <OpacityInput name="panelOpacity" v-model="panelOpacityLocal" :fallback="previewTheme.opacity.panel || 1"/> + <ColorInput name="panelTextColor" v-model="panelTextColorLocal" :fallback="previewTheme.colors.panelText" :label="$t('settings.text')"/> + <ContrastRatio :contrast="previewContrast.panelText" large="1"/> + <ColorInput name="panelLinkColor" v-model="panelLinkColorLocal" :fallback="previewTheme.colors.panelLink" :label="$t('settings.links')"/> + <ContrastRatio :contrast="previewContrast.panelLink" large="1"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4> + <ColorInput name="topBarColor" v-model="topBarColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/> + <ColorInput name="topBarTextColor" v-model="topBarTextColorLocal" :fallback="previewTheme.colors.topBarText" :label="$t('settings.text')"/> + <ContrastRatio :contrast="previewContrast.topBarText"/> + <ColorInput name="topBarLinkColor" v-model="topBarLinkColorLocal" :fallback="previewTheme.colors.topBarLink" :label="$t('settings.links')"/> + <ContrastRatio :contrast="previewContrast.topBarLink"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4> + <ColorInput name="inputColor" v-model="inputColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/> + <OpacityInput name="inputOpacity" v-model="inputOpacityLocal" :fallback="previewTheme.opacity.input || 1"/> + <ColorInput name="inputTextColor" v-model="inputTextColorLocal" :fallback="previewTheme.colors.inputText" :label="$t('settings.text')"/> + <ContrastRatio :contrast="previewContrast.inputText"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4> + <ColorInput name="btnColor" v-model="btnColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/> + <OpacityInput name="btnOpacity" v-model="btnOpacityLocal" :fallback="previewTheme.opacity.btn || 1"/> + <ColorInput name="btnTextColor" v-model="btnTextColorLocal" :fallback="previewTheme.colors.btnText" :label="$t('settings.text')"/> + <ContrastRatio :contrast="previewContrast.btnText"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4> + <ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" :label="$t('settings.style.common.color')"/> + <OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/> + </div> + <div class="color-item"> + <h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4> + <ColorInput name="faintColor" v-model="faintColorLocal" :fallback="previewTheme.colors.faint || 1" :label="$t('settings.text')"/> + <ColorInput name="faintLinkColor" v-model="faintLinkColorLocal" :fallback="previewTheme.colors.faintLink" :label="$t('settings.links')"/> + <ColorInput name="panelFaintColor" v-model="panelFaintColorLocal" :fallback="previewTheme.colors.panelFaint" :label="$t('settings.style.advanced_colors.panel_header')"/> + <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/> + </div> + </div> - <div class="radius-container"> - <p>{{$t('settings.radii_help')}}</p> - <div class="radius-item"> - <label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label> - <input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16"> - <input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal"> - </div> - <div class="radius-item"> - <label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label> - <input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16"> - <input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal"> - </div> - <div class="radius-item"> - <label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label> - <input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50"> - <input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal"> - </div> - <div class="radius-item"> - <label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label> - <input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28"> - <input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal"> - </div> - <div class="radius-item"> - <label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label> - <input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28"> - <input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal"> - </div> - <div class="radius-item"> - <label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label> - <input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50"> - <input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal"> - </div> - <div class="radius-item"> - <label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label> - <input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20"> - <input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal"> - </div> - </div> + <div :label="$t('settings.style.radii._tab_label')" class="radius-container"> + <div class="tab-header"> + <p>{{$t('settings.radii_help')}}</p> + <button class="btn" @click="clearRoundness">{{$t('settings.style.switcher.clear_all')}}</button> + </div> + <RangeInput name="btnRadius" :label="$t('settings.btnRadius')" v-model="btnRadiusLocal" :fallback="previewTheme.radii.btn" max="16" hardMin="0"/> + <RangeInput name="inputRadius" :label="$t('settings.inputRadius')" v-model="inputRadiusLocal" :fallback="previewTheme.radii.input" max="9" hardMin="0"/> + <RangeInput name="checkboxRadius" :label="$t('settings.checkboxRadius')" v-model="checkboxRadiusLocal" :fallback="previewTheme.radii.checkbox" max="16" hardMin="0"/> + <RangeInput name="panelRadius" :label="$t('settings.panelRadius')" v-model="panelRadiusLocal" :fallback="previewTheme.radii.panel" max="50" hardMin="0"/> + <RangeInput name="avatarRadius" :label="$t('settings.avatarRadius')" v-model="avatarRadiusLocal" :fallback="previewTheme.radii.avatar" max="28" hardMin="0"/> + <RangeInput name="avatarAltRadius" :label="$t('settings.avatarAltRadius')" v-model="avatarAltRadiusLocal" :fallback="previewTheme.radii.avatarAlt" max="28" hardMin="0"/> + <RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/> + <RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/> + </div> + + <div :label="$t('settings.style.shadows._tab_label')" class="shadow-container"> + <div class="tab-header shadow-selector"> + <div class="select-container"> + {{$t('settings.style.shadows.component')}} + <label for="shadow-switcher" class="select"> + <select id="shadow-switcher" v-model="shadowSelected" class="shadow-switcher"> + <option v-for="shadow in shadowsAvailable" + :value="shadow"> + {{$t('settings.style.shadows.components.' + shadow)}} + </option> + </select> + <i class="icon-down-open"/> + </label> + </div> + <div class="override"> + <label for="override" class="label"> + {{$t('settings.style.shadows.override')}} + </label> + <input + v-model="currentShadowOverriden" + name="override" + id="override" + class="input-override" + type="checkbox"> + <label class="checkbox-label" for="override"></label> + </div> + <button class="btn" @click="clearShadows">{{$t('settings.style.switcher.clear_all')}}</button> + </div> + <shadow-control :ready="!!currentShadowFallback" :fallback="currentShadowFallback" v-model="currentShadow"/> + <div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"> + <i18n path="settings.style.shadows.filter_hint.always_drop_shadow" tag="p"> + <code>filter: drop-shadow()</code> + </i18n> + <p>{{$t('settings.style.shadows.filter_hint.avatar_inset')}}</p> + <i18n path="settings.style.shadows.filter_hint.drop_shadow_syntax" tag="p"> + <code>drop-shadow</code> + <code>spread-radius</code> + <code>inset</code> + </i18n> + <i18n path="settings.style.shadows.filter_hint.inset_classic" tag="p"> + <code>box-shadow</code> + </i18n> + <p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p> + </div> + </div> + + <div :label="$t('settings.style.fonts._tab_label')" class="fonts-container"> + <div class="tab-header"> + <p>{{$t('settings.style.fonts.help')}}</p> + <button class="btn" @click="clearFonts">{{$t('settings.style.switcher.clear_all')}}</button> + </div> + <FontControl + name="ui" + v-model="fontsLocal.interface" + :label="$t('settings.style.fonts.components.interface')" + :fallback="previewTheme.fonts.interface" + no-inherit="1"/> + <FontControl + name="input" + v-model="fontsLocal.input" + :label="$t('settings.style.fonts.components.input')" + :fallback="previewTheme.fonts.input"/> + <FontControl + name="post" + v-model="fontsLocal.post" + :label="$t('settings.style.fonts.components.post')" + :fallback="previewTheme.fonts.post"/> + <FontControl + name="postCode" + v-model="fontsLocal.postCode" + :label="$t('settings.style.fonts.components.postCode')" + :fallback="previewTheme.fonts.postCode"/> + </div> + </tab-switcher> + </keep-alive> <div class="apply-container"> - <button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button> + <button class="btn submit" :disabled="!themeValid" @click="setCustomTheme">{{$t('general.apply')}}</button> + <button class="btn" @click="clearAll">{{$t('settings.style.switcher.reset')}}</button> </div> </div> </template> <script src="./style_switcher.js"></script> -<style lang="scss"> -@import '../../_variables.scss'; -.style-switcher { - margin-right: 1em; -} - -.import-warning { - color: $fallback--cRed; - color: var(--cRed, $fallback--cRed); -} - -.apply-container, -.radius-container, -.color-container, -.presets-container { - display: flex; - - p { - flex: 2 0 100%; - margin-top: 2em; - margin-bottom: .5em; - } -} - -.radius-container { - flex-direction: column; -} - -.color-container { - flex-wrap: wrap; - justify-content: space-between; -} - -.presets-container { - justify-content: center; - .import-export { - display: flex; - - .btn { - margin-left: .5em; - } - } -} - -.preview-container { - border-top: 1px dashed; - border-bottom: 1px dashed; - border-color: $fallback--border; - border-color: var(--border, $fallback--border); - margin: 1em -1em 0; - padding: 1em; - - .btn { - margin-top: 1em; - min-height: 30px; - width: 10em; - } -} - -.apply-container { - justify-content: center; -} - -.radius-item, -.color-item { - min-width: 20em; - display:flex; - flex: 1 1 0; - align-items: baseline; - margin: 5px 6px 5px 0; - - label { - color: var(--faint, $fallback--faint); - } -} - -.radius-item { - flex-basis: auto; -} - -.theme-radius-rn, -.theme-color-cl { - border: 0; - box-shadow: none; - background: transparent; - color: var(--faint, $fallback--faint); - align-self: stretch; -} - -.theme-color-cl, -.theme-radius-in, -.theme-color-in { - margin-left: 4px; -} - -.theme-color-in { - min-width: 4em; -} - -.theme-radius-in { - min-width: 1em; -} - -.theme-radius-in, -.theme-color-in { - max-width: 7em; - flex: 1; -} - -.theme-radius-lb, -.theme-color-lb { - flex: 2; - min-width: 7em; -} - -.theme-radius-lb{ - max-width: 50em; -} - -.theme-color-lb { - max-width: 10em; -} - -.theme-color-cl { - padding: 1px; - max-width: 8em; - height: 100%; - flex: 0; - min-width: 2em; - cursor: pointer; - max-height: 29px; -} - -.theme-preview-content { - padding: 20px; -} - -.dummy { - .avatar { - background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%); - color: black; - text-align: center; - height: 48px; - line-height: 48px; - width: 48px; - float: left; - margin-right: 1em; - } -} -</style> +<style src="./style_switcher.scss" lang="scss"></style> diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx @@ -25,11 +25,14 @@ export default Vue.component('tab-switcher', { } return (<button onClick={this.activateTab(index)} class={ classes.join(' ') }>{slot.data.attrs.label}</button>) }); - const contents = ( - <div> - {this.$slots.default.filter(slot => slot.data)[this.active]} - </div> - ); + const contents = this.$slots.default.filter(_=>_.data).map(( slot, index ) => { + const active = index === this.active + return ( + <div class={active ? 'active' : 'hidden'}> + {slot} + </div> + ) + }); return ( <div class="tab-switcher"> <div class="tabs"> diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss @@ -1,13 +1,21 @@ @import '../../_variables.scss'; .tab-switcher { + .contents { + .hidden { + display: none; + } + } .tabs { display: flex; position: relative; justify-content: center; width: 100%; - overflow: hidden; + overflow-y: hidden; + overflow-x: auto; padding-top: 5px; + height: 32px; + box-sizing: border-box; &::after, &::before { display: block; @@ -17,20 +25,34 @@ .tab, &::after, &::before { border-bottom: 1px solid; - border-bottom-color: $fallback--btn; - border-bottom-color: var(--btn, $fallback--btn); + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); } .tab { + position: relative; border-bottom-left-radius: 0; border-bottom-right-radius: 0; - padding: .3em 1em; + padding: 5px 1em 99px; + white-space: nowrap; &:not(.active) { - border-bottom: 1px solid; - border-bottom-color: $fallback--btn; - border-bottom-color: var(--btn, $fallback--btn); z-index: 4; + + &:hover { + z-index: 6; + } + + &::after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 26px; + border-bottom: 1px solid; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); + } } &.active { diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue @@ -10,7 +10,7 @@ <button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError"> {{$t('timeline.show_new')}}{{newStatusCountStr}} </button> - <div @click.prevent class="loadmore-text" v-if="!timeline.newStatusCount > 0 && !timelineError"> + <div @click.prevent class="loadmore-text faint" v-if="!timeline.newStatusCount > 0 && !timelineError"> {{$t('timeline.up_to_date')}} </div> </div> @@ -58,15 +58,7 @@ .timeline { .loadmore-text { - opacity: 0.8; - background-color: transparent; - color: $fallback--faint; - color: var(--faint, $fallback--faint); - } - - .loadmore-error { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + opacity: 1; } } @@ -79,7 +71,7 @@ border-color: var(--border, $fallback--border); padding: 10px; z-index: 1; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: $fallback--fg; + background-color: var(--panel, $fallback--fg); } </style> diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card_content/user_card_content.js @@ -7,14 +7,18 @@ export default { return { hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined' ? this.$store.state.instance.hideUserStats - : this.$store.state.config.hideUserStats + : this.$store.state.config.hideUserStats, + betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, computed: { headingStyle () { - const color = this.$store.state.config.colors.bg + const color = this.$store.state.config.customTheme.colors + ? this.$store.state.config.customTheme.colors.bg // v2 + : this.$store.state.config.colors.bg // v1 + if (color) { - const rgb = hex2rgb(color) + const rgb = (typeof color === 'string') ? hex2rgb(color) : color const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)` return { backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`, diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue @@ -10,7 +10,7 @@ </a> <div class='container'> <router-link :to="{ name: 'user-profile', params: { id: user.id } }"> - <StillImage class="avatar" :src="user.profile_image_url_original"/> + <StillImage class="avatar" :class='{ "better-shadow": betterShadow }' :src="user.profile_image_url_original"/> </router-link> <div class="name-and-screen-name"> <div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div> @@ -41,74 +41,74 @@ </div> </div> <div v-if="isOtherUser" class="user-interactions"> - <div class="follow" v-if="loggedIn"> - <span v-if="user.following"> - <!--Following them!--> - <button @click="unfollowUser" class="pressed"> - {{ $t('user_card.following') }} - </button> - </span> - <span v-if="!user.following"> - <button @click="followUser"> - {{ $t('user_card.follow') }} - </button> - </span> - </div> - <div class='mute' v-if='isOtherUser'> - <span v-if='user.muted'> - <button @click="toggleMute" class="pressed"> - {{ $t('user_card.muted') }} - </button> - </span> - <span v-if='!user.muted'> - <button @click="toggleMute"> - {{ $t('user_card.mute') }} - </button> - </span> - </div> - <div class="remote-follow" v-if='!loggedIn && user.is_local'> - <form method="POST" :action='subscribeUrl'> - <input type="hidden" name="nickname" :value="user.screen_name"> - <input type="hidden" name="profile" value=""> - <button click="submit" class="remote-button"> - {{ $t('user_card.remote_follow') }} - </button> - </form> - </div> - <div class='block' v-if='isOtherUser && loggedIn'> - <span v-if='user.statusnet_blocking'> - <button @click="unblockUser" class="pressed"> - {{ $t('user_card.blocked') }} - </button> - </span> - <span v-if='!user.statusnet_blocking'> - <button @click="blockUser"> - {{ $t('user_card.block') }} - </button> - </span> - </div> + <div class="follow" v-if="loggedIn"> + <span v-if="user.following"> + <!--Following them!--> + <button @click="unfollowUser" class="pressed"> + {{ $t('user_card.following') }} + </button> + </span> + <span v-if="!user.following"> + <button @click="followUser"> + {{ $t('user_card.follow') }} + </button> + </span> </div> - </div> - </div> - <div class="panel-body profile-panel-body"> - <div v-if="!hideUserStatsLocal || switcher" class="user-counts" :class="{clickable: switcher}"> - <div class="user-count" v-on:click.prevent="setProfileView('statuses')" :class="{selected: selected === 'statuses'}"> - <h5>{{ $t('user_card.statuses') }}</h5> - <span v-if="!hideUserStatsLocal">{{user.statuses_count}} <br></span> + <div class='mute' v-if='isOtherUser'> + <span v-if='user.muted'> + <button @click="toggleMute" class="pressed"> + {{ $t('user_card.muted') }} + </button> + </span> + <span v-if='!user.muted'> + <button @click="toggleMute"> + {{ $t('user_card.mute') }} + </button> + </span> </div> - <div class="user-count" v-on:click.prevent="setProfileView('friends')" :class="{selected: selected === 'friends'}"> - <h5>{{ $t('user_card.followees') }}</h5> - <span v-if="!hideUserStatsLocal">{{user.friends_count}}</span> + <div class="remote-follow" v-if='!loggedIn && user.is_local'> + <form method="POST" :action='subscribeUrl'> + <input type="hidden" name="nickname" :value="user.screen_name"> + <input type="hidden" name="profile" value=""> + <button click="submit" class="remote-button"> + {{ $t('user_card.remote_follow') }} + </button> + </form> </div> - <div class="user-count" v-on:click.prevent="setProfileView('followers')" :class="{selected: selected === 'followers'}"> - <h5>{{ $t('user_card.followers') }}</h5> - <span v-if="!hideUserStatsLocal">{{user.followers_count}}</span> + <div class='block' v-if='isOtherUser && loggedIn'> + <span v-if='user.statusnet_blocking'> + <button @click="unblockUser" class="pressed"> + {{ $t('user_card.blocked') }} + </button> + </span> + <span v-if='!user.statusnet_blocking'> + <button @click="blockUser"> + {{ $t('user_card.block') }} + </button> + </span> </div> </div> - <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p> - <p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p> </div> </div> + <div class="panel-body profile-panel-body"> + <div v-if="!hideUserStatsLocal || switcher" class="user-counts" :class="{clickable: switcher}"> + <div class="user-count" v-on:click.prevent="setProfileView('statuses')" :class="{selected: selected === 'statuses'}"> + <h5>{{ $t('user_card.statuses') }}</h5> + <span v-if="!hideUserStatsLocal">{{user.statuses_count}} <br></span> + </div> + <div class="user-count" v-on:click.prevent="setProfileView('friends')" :class="{selected: selected === 'friends'}"> + <h5>{{ $t('user_card.followees') }}</h5> + <span v-if="!hideUserStatsLocal">{{user.friends_count}}</span> + </div> + <div class="user-count" v-on:click.prevent="setProfileView('followers')" :class="{selected: selected === 'followers'}"> + <h5>{{ $t('user_card.followers') }}</h5> + <span v-if="!hideUserStatsLocal">{{user.followers_count}}</span> + </div> + </div> + <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p> + <p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p> + </div> +</div> </template> <script src="./user_card_content.js"></script> @@ -120,10 +120,12 @@ background-size: cover; border-radius: $fallback--panelRadius; border-radius: var(--panelRadius, $fallback--panelRadius); + overflow: hidden; .panel-heading { padding: 0.6em 0em; text-align: center; + box-shadow: none; } } @@ -138,15 +140,14 @@ } .user-info { - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); padding: 0 16px; .container { padding: 16px 10px 6px 10px; display: flex; max-height: 56px; - overflow: hidden; .avatar { border-radius: $fallback--avatarRadius; @@ -155,8 +156,14 @@ width: 56px; height: 56px; box-shadow: 0px 1px 8px rgba(0,0,0,0.75); + box-shadow: var(--avatarShadow); object-fit: cover; + &.better-shadow { + box-shadow: var(--avatarShadowInset); + filter: var(--avatarShadowFilter) + } + &.animated::before { display: none; } @@ -173,8 +180,8 @@ } .usersettings { - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); opacity: .8; } @@ -185,6 +192,16 @@ text-overflow: ellipsis; white-space: nowrap; flex: 1 1 0; + // This is so that text doesn't get overlapped by avatar's shadow if it has + // big one + z-index: 1; + + img { + width: 26px; + height: 26px; + vertical-align: middle; + object-fit: contain + } } .user-name{ @@ -193,8 +210,8 @@ } .user-screen-name { - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); display: inline-block; font-weight: light; font-size: 15px; @@ -269,8 +286,8 @@ padding: .5em 1.5em 0em 1.5em; text-align: center; justify-content: space-between; - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); &.clickable { .user-count { diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -130,6 +130,7 @@ "import_followers_from_a_csv_file": "Import follows from a csv file", "import_theme": "Load preset", "inputRadius": "Input fields", + "checkboxRadius": "Checkboxes", "instance_default": "(default: {value})", "instance_default_simple" : "(default)", "interface": "Interface", @@ -180,11 +181,122 @@ "text": "Text", "theme": "Theme", "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", + "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", + "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "tooltipRadius": "Tooltips/alerts", "user_settings": "User Settings", "values": { "false": "no", "true": "yes" + }, + "style": { + "switcher": { + "keep_color": "Keep colors", + "keep_shadows": "Keep shadows", + "keep_opacity": "Keep opacity", + "keep_roundness": "Keep roundness", + "keep_fonts": "Keep fonts", + "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", + "reset": "Reset", + "clear_all": "Clear all", + "clear_opacity": "Clear opacity" + }, + "common": { + "color": "Color", + "opacity": "Opacity", + "contrast": { + "hint": "Contrast ratio is {ratio}, it {level} {context}", + "level": { + "aa": "meets Level AA guideline (minimal)", + "aaa": "meets Level AAA guideline (recommended)", + "bad": "doesn't meet any accessibility guidelines" + }, + "context": { + "18pt": "for large (18pt+) text", + "text": "for text" + } + } + }, + "common_colors": { + "_tab_label": "Common", + "main": "Common colors", + "foreground_hint": "See \"Advanced\" tab for more detailed control", + "rgbo": "Icons, accents, badges" + }, + "advanced_colors": { + "_tab_label": "Advanced", + "alert": "Alert background", + "alert_error": "Error", + "badge": "Badge background", + "badge_notification": "Notification", + "panel_header": "Panel header", + "top_bar": "Top bar", + "borders": "Borders", + "buttons": "Buttons", + "inputs": "Input fields", + "faint_text": "Faded text" + }, + "radii": { + "_tab_label": "Roundness" + }, + "shadows": { + "_tab_label": "Shadow and lighting", + "component": "Component", + "override": "Override", + "shadow_id": "Shadow #{value}", + "blur": "Blur", + "spread": "Spread", + "inset": "Inset", + "hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", + "filter_hint": { + "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", + "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", + "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", + "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", + "inset_classic": "Inset shadows will be using {0}" + }, + "components": { + "panel": "Panel", + "panelHeader": "Panel header", + "topBar": "Top bar", + "avatar": "User avatar (in profile view)", + "avatarStatus": "User avatar (in post display)", + "popup": "Popups and tooltips", + "button": "Button", + "buttonHover": "Button (hover)", + "buttonPressed": "Button (pressed)", + "buttonPressedHover": "Button (pressed+hover)", + "input": "Input field" + } + }, + "fonts": { + "_tab_label": "Fonts", + "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.", + "components": { + "interface": "Interface", + "input": "Input fields", + "post": "Post text", + "postCode": "Monospaced text in a post (rich text)" + }, + "family": "Font name", + "size": "Size (in px)", + "weight": "Weight (boldness)", + "custom": "Custom" + }, + "preview": { + "header": "Preview", + "content": "Content", + "error": "Example error", + "button": "Button", + "text": "A bunch of more {0} and {1}", + "mono": "content", + "input": "Just landed in L.A.", + "faint_link": "helpful manual", + "fine_print": "Read our {0} to learn nothing useful!", + "header_faint": "This is fine", + "checkbox": "I have skimmed over terms and conditions", + "link": "a nice lil' link" + } } }, "timeline": { diff --git a/src/i18n/ru.json b/src/i18n/ru.json @@ -109,6 +109,7 @@ "import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv", "import_theme": "Загрузить Тему", "inputRadius": "Поля ввода", + "checkboxRadius": "Чекбоксы", "interface": "Интерфейс", "interfaceLanguage": "Язык интерфейса", "limited_availability": "Не доступно в вашем браузере", @@ -149,8 +150,119 @@ "text": "Текст", "theme": "Тема", "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.", + "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения", + "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.", "tooltipRadius": "Всплывающие подсказки/уведомления", - "user_settings": "Настройки пользователя" + "user_settings": "Настройки пользователя", + "style": { + "switcher": { + "keep_color": "Оставить цвета", + "keep_shadows": "Оставить тени", + "keep_opacity": "Оставить прозрачность", + "keep_roundness": "Оставить скругление", + "keep_fonts": "Оставить шрифты", + "save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.", + "reset": "Сбросить", + "clear_all": "Очистить всё", + "clear_opacity": "Очистить прозрачность" + }, + "common": { + "color": "Цвет", + "opacity": "Прозрачность", + "contrast": { + "hint": "Уровень контраста: {ratio}, что {level} {context}", + "level": { + "aa": "соответствует гайдлайну Level AA (минимальный)", + "aaa": "соответствует гайдлайну Level AAA (рекомендуемый)", + "bad": "не соответствует каким либо гайдлайнам" + }, + "context": { + "18pt": "для крупного (18pt+) текста", + "text": "для текста" + } + } + }, + "common_colors": { + "_tab_label": "Общие", + "main": "Общие цвета", + "foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля", + "rgbo": "Иконки, акценты, ярылки" + }, + "advanced_colors": { + "_tab_label": "Дополнительно", + "alert": "Фон уведомлений", + "alert_error": "Ошибки", + "badge": "Фон значков", + "badge_notification": "Уведомления", + "panel_header": "Заголовок панели", + "top_bar": "Верняя полоска", + "borders": "Границы", + "buttons": "Кнопки", + "inputs": "Поля ввода", + "faint_text": "Маловажный текст" + }, + "radii": { + "_tab_label": "Скругление" + }, + "shadows": { + "_tab_label": "Светотень", + "component": "Компонент", + "override": "Переопределить", + "shadow_id": "Тень №{value}", + "blur": "Размытие", + "spread": "Разброс", + "inset": "Внутренняя", + "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.", + "filter_hint": { + "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это", + "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}", + "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете", + "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0", + "inset_classic": "Внутренние тени будут использовать {0}" + }, + "components": { + "panel": "Панель", + "panelHeader": "Заголовок панели", + "topBar": "Верхняя полоска", + "avatar": "Аватарка (профиль)", + "avatarStatus": "Аватарка (в ленте)", + "popup": "Всплывающие подсказки", + "button": "Кнопки", + "buttonHover": "Кнопки (наведен курсор)", + "buttonPressed": "Кнопки (нажата)", + "buttonPressedHover": "Кнопки (нажата+наведен курсор)", + "input": "Поля ввода" + } + }, + "fonts": { + "_tab_label": "Шрифты", + "help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.", + "components": { + "interface": "Интерфейс", + "input": "Поля ввода", + "post": "Текст постов", + "postCode": "Моноширинный текст в посте (форматирование)" + }, + "family": "Шрифт", + "size": "Размер (в пикселях)", + "weight": "Ширина", + "custom": "Другой" + }, + "preview": { + "header": "Пример", + "content": "Контент", + "error": "Ошибка стоп 000", + "button": "Кнопка", + "text": "Еще немного {0} и масенькая {1}", + "mono": "контента", + "input": "Что нового?", + "faint_link": "Его придется убрать", + "fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.", + "header_faint": "Все идет по плану", + "checkbox": "Я подтверждаю что не было ни единого разрыва", + "link": "ссылка" + } + } }, "timeline": { "collapse": "Свернуть", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js @@ -75,6 +75,7 @@ export default function createPersistedState ({ loaded = true } catch (e) { console.log("Couldn't load state") + console.error(e) loaded = true } subscriber(store)((mutation, state) => { diff --git a/src/modules/config.js b/src/modules/config.js @@ -1,5 +1,5 @@ import { set, delete as del } from 'vue' -import StyleSetter from '../services/style_setter/style_setter.js' +import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' const browserLocale = (window.navigator.language || 'en').split('-')[0] @@ -54,10 +54,10 @@ const config = { commit('setOption', {name, value}) switch (name) { case 'theme': - StyleSetter.setPreset(value, commit) + setPreset(value, commit) break case 'customTheme': - StyleSetter.setColors(value, commit) + applyTheme(value, commit) } } } diff --git a/src/modules/instance.js b/src/modules/instance.js @@ -1,5 +1,5 @@ import { set } from 'vue' -import StyleSetter from '../services/style_setter/style_setter.js' +import { setPreset } from '../services/style_setter/style_setter.js' const defaultState = { // Stuff from static/config.json and apiConfig @@ -60,7 +60,7 @@ const instance = { dispatch('setPageTitle') break case 'theme': - StyleSetter.setPreset(value, commit) + setPreset(value, commit) } } } diff --git a/src/modules/interface.js b/src/modules/interface.js @@ -4,6 +4,12 @@ const defaultState = { settings: { currentSaveStateNotice: null, noticeClearTimeout: null + }, + browserSupport: { + cssFilter: window.CSS && window.CSS.supports && ( + window.CSS.supports('filter', 'drop-shadow(0 0)') || + window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') + ) } } diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js @@ -1,6 +1,15 @@ import { map } from 'lodash' const rgb2hex = (r, g, b) => { + if (r === null || typeof r === 'undefined') { + return undefined + } + if (r[0] === '#') { + return r + } + if (typeof r === 'object') { + ({ r, g, b } = r) + } [r, g, b] = map([r, g, b], (val) => { val = Math.ceil(val) val = val < 0 ? 0 : val @@ -10,6 +19,91 @@ const rgb2hex = (r, g, b) => { return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}` } +/** + * Converts 8-bit RGB component into linear component + * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + * https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml + * https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation + * + * @param {Number} bit - color component [0..255] + * @returns {Number} linear component [0..1] + */ +const c2linear = (bit) => { + // W3C gives 0.03928 while wikipedia states 0.04045 + // what those magical numbers mean - I don't know. + // something about gamma-correction, i suppose. + // Sticking with W3C example. + const c = bit / 255 + if (c < 0.03928) { + return c / 12.92 + } else { + return Math.pow((c + 0.055) / 1.055, 2.4) + } +} + +/** + * Converts sRGB into linear RGB + * @param {Object} srgb - sRGB color + * @returns {Object} linear rgb color + */ +const srgbToLinear = (srgb) => { + return 'rgb'.split('').reduce((acc, c) => { acc[c] = c2linear(srgb[c]); return acc }, {}) +} + +/** + * Calculates relative luminance for given color + * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + * https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml + * + * @param {Object} srgb - sRGB color + * @returns {Number} relative luminance + */ +const relativeLuminance = (srgb) => { + const {r, g, b} = srgbToLinear(srgb) + return 0.2126 * r + 0.7152 * g + 0.0722 * b +} + +/** + * Generates color ratio between two colors. Order is unimporant + * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef + * + * @param {Object} a - sRGB color + * @param {Object} b - sRGB color + * @returns {Number} color ratio + */ +const getContrastRatio = (a, b) => { + const la = relativeLuminance(a) + const lb = relativeLuminance(b) + const [l1, l2] = la > lb ? [la, lb] : [lb, la] + + return (l1 + 0.05) / (l2 + 0.05) +} + +/** + * This performs alpha blending between solid background and semi-transparent foreground + * + * @param {Object} fg - top layer color + * @param {Number} fga - top layer's alpha + * @param {Object} bg - bottom layer color + * @returns {Object} sRGB of resulting color + */ +const alphaBlend = (fg, fga, bg) => { + if (fga === 1 || typeof fga === 'undefined') return fg + return 'rgb'.split('').reduce((acc, c) => { + // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending + // for opaque bg and transparent fg + acc[c] = (fg[c] * fga + bg[c] * (1 - fga)) + return acc + }, {}) +} + +const invert = (rgb) => { + return 'rgb'.split('').reduce((acc, c) => { + acc[c] = 255 - rgb[c] + return acc + }, {}) +} + const hex2rgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? { @@ -19,16 +113,18 @@ const hex2rgb = (hex) => { } : null } -const rgbstr2hex = (rgb) => { - if (rgb[0] === '#') { - return rgb - } - rgb = rgb.match(/\d+/g) - return `#${((Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16)}` +const mixrgb = (a, b) => { + return Object.keys(a).reduce((acc, k) => { + acc[k] = (a[k] + b[k]) / 2 + return acc + }, {}) } export { rgb2hex, hex2rgb, - rgbstr2hex + mixrgb, + invert, + getContrastRatio, + alphaBlend } diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js @@ -1,5 +1,6 @@ import { times } from 'lodash' -import { rgb2hex, hex2rgb } from '../color_convert/color_convert.js' +import { brightness, invertLightness, convert, contrastRatio } from 'chromatism' +import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js' // 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 @@ -39,8 +40,6 @@ const setStyle = (href, commit) => { colors[name] = color }) - commit('setOption', { name: 'colors', value: colors }) - body.removeChild(baseEl) const styleEl = document.createElement('style') @@ -53,7 +52,27 @@ const setStyle = (href, commit) => { cssEl.addEventListener('load', setDynamic) } -const setColors = (col, commit) => { +const rgb2rgba = function (rgba) { + return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})` +} + +const getTextColor = function (bg, text, preserve) { + const bgIsLight = convert(bg).hsl.l > 50 + const textIsLight = convert(text).hsl.l > 50 + + if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) { + const base = typeof text.a !== 'undefined' ? { a: text.a } : {} + const result = Object.assign(base, invertLightness(text).rgb) + if (!preserve && getContrastRatio(bg, result) < 4.5) { + return contrastRatio(bg, text).rgb + } + return result + } + return text +} + +const applyTheme = (input, commit) => { + const { rules, theme } = generatePreset(input) const head = document.head const body = document.body body.style.display = 'none' @@ -62,56 +81,411 @@ const setColors = (col, commit) => { head.appendChild(styleEl) const styleSheet = styleEl.sheet - const isDark = (col.text.r + col.text.g + col.text.b) > (col.bg.r + col.bg.g + col.bg.b) - let colors = {} - let radii = {} + styleSheet.toString() + styleSheet.insertRule(`body { ${rules.radii} }`, 'index-max') + styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max') + styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max') + styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max') + body.style.display = 'initial' - const mod = isDark ? -10 : 10 + // commit('setOption', { name: 'colors', value: htmlColors }) + // commit('setOption', { name: 'radii', value: radii }) + commit('setOption', { name: 'customTheme', value: input }) + commit('setOption', { name: 'colors', value: theme.colors }) +} - colors.bg = rgb2hex(col.bg.r, col.bg.g, col.bg.b) // background - colors.lightBg = rgb2hex((col.bg.r + col.fg.r) / 2, (col.bg.g + col.fg.g) / 2, (col.bg.b + col.fg.b) / 2) // hilighted bg - colors.btn = rgb2hex(col.fg.r, col.fg.g, col.fg.b) // panels & buttons - colors.input = `rgba(${col.fg.r}, ${col.fg.g}, ${col.fg.b}, .5)` - colors.border = rgb2hex(col.fg.r - mod, col.fg.g - mod, col.fg.b - mod) // borders - colors.faint = `rgba(${col.text.r}, ${col.text.g}, ${col.text.b}, .5)` - colors.fg = rgb2hex(col.text.r, col.text.g, col.text.b) // text - colors.lightFg = rgb2hex(col.text.r - mod * 5, col.text.g - mod * 5, col.text.b - mod * 5) // strong text +const getCssShadow = (input, usesDropShadow) => { + if (input.length === 0) { + return 'none' + } - colors['base07'] = rgb2hex(col.text.r - mod * 2, col.text.g - mod * 2, col.text.b - mod * 2) + return input + .filter(_ => usesDropShadow ? _.inset : _) + .map((shad) => [ + shad.x, + shad.y, + shad.blur, + shad.spread + ].map(_ => _ + 'px').concat([ + getCssColor(shad.color, shad.alpha), + shad.inset ? 'inset' : '' + ]).join(' ')).join(', ') +} - colors.link = rgb2hex(col.link.r, col.link.g, col.link.b) // links - colors.icon = rgb2hex((col.bg.r + col.text.r) / 2, (col.bg.g + col.text.g) / 2, (col.bg.b + col.text.b) / 2) // icons +const getCssShadowFilter = (input) => { + if (input.length === 0) { + return 'none' + } - colors.cBlue = col.cBlue && rgb2hex(col.cBlue.r, col.cBlue.g, col.cBlue.b) - colors.cRed = col.cRed && rgb2hex(col.cRed.r, col.cRed.g, col.cRed.b) - colors.cGreen = col.cGreen && rgb2hex(col.cGreen.r, col.cGreen.g, col.cGreen.b) - colors.cOrange = col.cOrange && rgb2hex(col.cOrange.r, col.cOrange.g, col.cOrange.b) + return input + // drop-shadow doesn't support inset or spread + .filter((shad) => !shad.inset && Number(shad.spread) === 0) + .map((shad) => [ + shad.x, + shad.y, + // drop-shadow's blur is twice as strong compared to box-shadow + shad.blur / 2 + ].map(_ => _ + 'px').concat([ + getCssColor(shad.color, shad.alpha) + ]).join(' ')) + .map(_ => `drop-shadow(${_})`) + .join(' ') +} - colors.cAlertRed = col.cRed && `rgba(${col.cRed.r}, ${col.cRed.g}, ${col.cRed.b}, .5)` +const getCssColor = (input, a) => { + let rgb = {} + if (typeof input === 'object') { + rgb = input + } else if (typeof input === 'string') { + if (input.startsWith('#')) { + rgb = hex2rgb(input) + } else if (input.startsWith('--')) { + return `var(${input})` + } else { + return input + } + } + return rgb2rgba({ ...rgb, a }) +} - radii.btnRadius = col.btnRadius - radii.inputRadius = col.inputRadius - radii.panelRadius = col.panelRadius - radii.avatarRadius = col.avatarRadius - radii.avatarAltRadius = col.avatarAltRadius - radii.tooltipRadius = col.tooltipRadius - radii.attachmentRadius = col.attachmentRadius +const generateColors = (input) => { + const colors = {} + const opacity = Object.assign({ + alert: 0.5, + input: 0.5, + faint: 0.5 + }, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => { + if (typeof v !== 'undefined') { + acc[k] = v + } + return acc + }, {})) + const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => { + if (typeof v === 'object') { + acc[k] = v + } else { + acc[k] = hex2rgb(v) + } + return acc + }, {}) - styleSheet.toString() - styleSheet.insertRule(`body { ${Object.entries(colors).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}`).join(';')} }`, 'index-max') - styleSheet.insertRule(`body { ${Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}px`).join(';')} }`, 'index-max') - body.style.display = 'initial' + const isLightOnDark = convert(col.bg).hsl.l < convert(col.text).hsl.l + const mod = isLightOnDark ? 1 : -1 + + colors.text = col.text + colors.lightText = brightness(20 * mod, colors.text).rgb + colors.link = col.link + colors.faint = col.faint || Object.assign({}, col.text) + + colors.bg = col.bg + colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb + + colors.fg = col.fg + colors.fgText = col.fgText || getTextColor(colors.fg, colors.text) + colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true) + + colors.border = col.border || brightness(2 * mod, colors.fg).rgb + + colors.btn = col.btn || Object.assign({}, col.fg) + colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText) + + colors.input = col.input || Object.assign({}, col.fg) + colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText) + + colors.panel = col.panel || Object.assign({}, col.fg) + colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText) + colors.panelLink = col.panelLink || getTextColor(colors.panel, colors.fgLink) + colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint) - commit('setOption', { name: 'colors', value: colors }) - commit('setOption', { name: 'radii', value: radii }) - commit('setOption', { name: 'customTheme', value: col }) + colors.topBar = col.topBar || Object.assign({}, col.fg) + colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText) + colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink) + + colors.faintLink = col.faintLink || Object.assign({}, col.link) + + colors.icon = mixrgb(colors.bg, colors.text) + + colors.cBlue = col.cBlue || hex2rgb('#0000FF') + colors.cRed = col.cRed || hex2rgb('#FF0000') + colors.cGreen = col.cGreen || hex2rgb('#00FF00') + colors.cOrange = col.cOrange || hex2rgb('#E3FF00') + + colors.alertError = col.alertError || Object.assign({}, colors.cRed) + colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text) + colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText) + + colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed) + colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb + + Object.entries(opacity).forEach(([ k, v ]) => { + if (typeof v === 'undefined') return + if (k === 'alert') { + colors.alertError.a = v + return + } + if (k === 'faint') { + colors[k + 'Link'].a = v + colors['panelFaint'].a = v + } + if (k === 'bg') { + colors['lightBg'].a = v + } + if (colors[k]) { + colors[k].a = v + } else { + console.error('Wrong key ' + k) + } + }) + + const htmlColors = Object.entries(colors) + .reduce((acc, [k, v]) => { + if (!v) return acc + acc.solid[k] = rgb2hex(v) + acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v) + return acc + }, { complete: {}, solid: {} }) + return { + rules: { + colors: Object.entries(htmlColors.complete) + .filter(([k, v]) => v) + .map(([k, v]) => `--${k}: ${v}`) + .join(';') + }, + theme: { + colors: htmlColors.solid, + opacity + } + } } -const setPreset = (val, commit) => { - window.fetch('/static/styles.json') +const generateRadii = (input) => { + let inputRadii = input.radii || {} + // v1 -> v2 + if (typeof input.btnRadius !== 'undefined') { + inputRadii = Object + .entries(input) + .filter(([k, v]) => k.endsWith('Radius')) + .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {}) + } + const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, { + btn: 4, + input: 4, + checkbox: 2, + panel: 10, + avatar: 5, + avatarAlt: 50, + tooltip: 2, + attachment: 5 + }) + + return { + rules: { + radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';') + }, + theme: { + radii + } + } +} + +const generateFonts = (input) => { + const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, acc[k]) + return acc + }, { + interface: { + family: 'sans-serif' + }, + input: { + family: 'inherit' + }, + post: { + family: 'inherit' + }, + postCode: { + family: 'monospace' + } + }) + + return { + rules: { + fonts: Object + .entries(fonts) + .filter(([k, v]) => v) + .map(([k, v]) => `--${k}Font: ${v.family}`).join(';') + }, + theme: { + fonts + } + } +} + +const generateShadows = (input) => { + const border = (top, shadow) => ({ + x: 0, + y: top ? 1 : -1, + blur: 0, + spread: 0, + color: shadow ? '#000000' : '#FFFFFF', + alpha: 0.2, + inset: true + }) + const buttonInsetFakeBorders = [border(true, false), border(false, true)] + const inputInsetFakeBorders = [border(true, true), border(false, false)] + const hoverGlow = { + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '--faint', + alpha: 1 + } + + const shadows = { + panel: [{ + x: 1, + y: 1, + blur: 4, + 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: [], + button: [{ + x: 0, + y: 0, + blur: 2, + spread: 0, + color: '#000000', + alpha: 1 + }, ...buttonInsetFakeBorders], + buttonHover: [hoverGlow, ...buttonInsetFakeBorders], + buttonPressed: [hoverGlow, ...inputInsetFakeBorders], + input: [...inputInsetFakeBorders, { + x: 0, + y: 0, + blur: 2, + inset: true, + spread: 0, + color: '#000000', + alpha: 1 + }], + ...(input.shadows || {}) + } + + return { + rules: { + shadows: Object + .entries(shadows) + // TODO for v2.1: if shadow doesn't have non-inset shadows with spread > 0 - optionally + // convert all non-inset shadows into filter: drop-shadow() to boost performance + .map(([k, v]) => [ + `--${k}Shadow: ${getCssShadow(v)}`, + `--${k}ShadowFilter: ${getCssShadowFilter(v)}`, + `--${k}ShadowInset: ${getCssShadow(v, true)}` + ].join(';')) + .join(';') + }, + theme: { + shadows + } + } +} + +const composePreset = (colors, radii, shadows, fonts) => { + return { + rules: { + ...shadows.rules, + ...colors.rules, + ...radii.rules, + ...fonts.rules + }, + theme: { + ...shadows.theme, + ...colors.theme, + ...radii.theme, + ...fonts.theme + } + } +} + +const generatePreset = (input) => { + const shadows = generateShadows(input) + const colors = generateColors(input) + const radii = generateRadii(input) + const fonts = generateFonts(input) + + return composePreset(colors, radii, shadows, fonts) +} + +const getThemes = () => { + return window.fetch('/static/styles.json') .then((data) => data.json()) .then((themes) => { - const theme = themes[val] ? themes[val] : themes['pleroma-dark'] + return Promise.all(Object.entries(themes).map(([k, v]) => { + if (typeof v === 'object') { + return Promise.resolve([k, v]) + } else if (typeof v === 'string') { + return window.fetch(v) + .then((data) => data.json()) + .then((theme) => { + return [k, theme] + }) + .catch((e) => { + console.error(e) + return [] + }) + } + })) + }) + .then((promises) => { + return promises + .filter(([k, v]) => v) + .reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, {}) + }) +} + +const setPreset = (val, commit) => { + getThemes().then((themes) => { + const theme = themes[val] ? themes[val] : themes['pleroma-dark'] + const isV1 = Array.isArray(theme) + const data = isV1 ? {} : theme.theme + + if (isV1) { const bgRgb = hex2rgb(theme[1]) const fgRgb = hex2rgb(theme[2]) const textRgb = hex2rgb(theme[3]) @@ -122,7 +496,7 @@ const setPreset = (val, commit) => { const cBlueRgb = hex2rgb(theme[7] || '#0000FF') const cOrangeRgb = hex2rgb(theme[8] || '#E3FF00') - const col = { + data.colors = { bg: bgRgb, fg: fgRgb, text: textRgb, @@ -132,23 +506,32 @@ const setPreset = (val, commit) => { cGreen: cGreenRgb, cOrange: cOrangeRgb } + } - // This is a hack, this function is only called during initial load. - // We want to cancel loading the theme from config.json if we're already - // loading a theme from the persisted state. - // Needed some way of dealing with the async way of things. - // load config -> set preset -> wait for styles.json to load -> - // load persisted state -> set colors -> styles.json loaded -> set colors - if (!window.themeLoaded) { - setColors(col, commit) - } - }) + // This is a hack, this function is only called during initial load. + // We want to cancel loading the theme from config.json if we're already + // loading a theme from the persisted state. + // Needed some way of dealing with the async way of things. + // load config -> set preset -> wait for styles.json to load -> + // load persisted state -> set colors -> styles.json loaded -> set colors + if (!window.themeLoaded) { + applyTheme(data, commit) + } + }) } -const StyleSetter = { +export { setStyle, setPreset, - setColors + applyTheme, + getTextColor, + generateColors, + generateRadii, + generateShadows, + generateFonts, + generatePreset, + getThemes, + composePreset, + getCssShadow, + getCssShadowFilter } - -export default StyleSetter diff --git a/src/services/user_highlighter/user_highlighter.js b/src/services/user_highlighter/user_highlighter.js @@ -11,7 +11,7 @@ const highlightStyle = (prefs) => { if (type === 'striped') { return { backgroundImage: [ - 'repeating-linear-gradient(-45deg,', + 'repeating-linear-gradient(135deg,', `${tintColor} ,`, `${tintColor} 20px,`, `${tintColor2} 20px,`, diff --git a/static/font/config.json b/static/font/config.json @@ -1,5 +1,4 @@ { - "name": "", "css_prefix_text": "icon-", "css_use_suffix": false, "hinting": true, @@ -51,7 +50,7 @@ { "uid": "09feb4465d9bd1364f4e301c9ddbaa92", "css": "retweet", - "code": 59396, + "code": 59398, "src": "fontawesome" }, { @@ -67,12 +66,6 @@ "src": "fontawesome" }, { - "uid": "1a5cfa186647e8c929c2b17b9fc4dac1", - "css": "plus-squared", - "code": 59398, - "src": "font-awesome" - }, - { "uid": "e99461abfef3923546da8d745372c995", "css": "cog", "code": 59399, @@ -191,6 +184,36 @@ "css": "brush", "code": 59411, "src": "iconic" + }, + { + "uid": "ca90da02d2c6a3183f2458e4dc416285", + "css": "adjust", + "code": 59396, + "src": "fontawesome" + }, + { + "uid": "5e2ab018e3044337bcef5f7e94098ea1", + "css": "thumbs-up-alt", + "code": 61796, + "src": "fontawesome" + }, + { + "uid": "c76b7947c957c9b78b11741173c8349b", + "css": "attention", + "code": 59412, + "src": "fontawesome" + }, + { + "uid": "1a5cfa186647e8c929c2b17b9fc4dac1", + "css": "plus-squared", + "code": 61694, + "src": "fontawesome" + }, + { + "uid": "44e04715aecbca7f266a17d5a7863c68", + "css": "plus", + "code": 59413, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/static/font/css/fontello-codes.css b/static/font/css/fontello-codes.css @@ -3,9 +3,9 @@ .icon-upload:before { content: '\e801'; } /* '' */ .icon-star:before { content: '\e802'; } /* '' */ .icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ +.icon-adjust:before { content: '\e804'; } /* '' */ .icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-plus-squared:before { content: '\e806'; } /* '' */ +.icon-retweet:before { content: '\e806'; } /* '' */ .icon-cog:before { content: '\e807'; } /* '' */ .icon-logout:before { content: '\e808'; } /* '' */ .icon-down-open:before { content: '\e809'; } /* '' */ @@ -19,6 +19,8 @@ .icon-lock:before { content: '\e811'; } /* '' */ .icon-globe:before { content: '\e812'; } /* '' */ .icon-brush:before { content: '\e813'; } /* '' */ +.icon-attention:before { content: '\e814'; } /* '' */ +.icon-plus:before { content: '\e815'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ @@ -26,7 +28,9 @@ .icon-menu:before { content: '\f0c9'; } /* '' */ .icon-mail-alt:before { content: '\f0e0'; } /* '' */ .icon-comment-empty:before { content: '\f0e5'; } /* '' */ +.icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ +.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ .icon-binoculars:before { content: '\f1e5'; } /* '' */ .icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/css/fontello-embedded.css b/static/font/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?99189355'); - src: url('../font/fontello.eot?99189355#iefix') format('embedded-opentype'), - url('../font/fontello.svg?99189355#fontello') format('svg'); + src: url('../font/fontello.eot?4112743'); + src: url('../font/fontello.eot?4112743#iefix') format('embedded-opentype'), + url('../font/fontello.svg?4112743#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,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') format('woff'), - url('data:application/octet-stream;base64,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') format('truetype'); + src: url('data:application/octet-stream;base64,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') format('woff'), + url('data:application/octet-stream;base64,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') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?99189355#fontello') format('svg'); + src: url('../font/fontello.svg?4112743#fontello') format('svg'); } } */ @@ -56,9 +56,9 @@ .icon-upload:before { content: '\e801'; } /* '' */ .icon-star:before { content: '\e802'; } /* '' */ .icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ +.icon-adjust:before { content: '\e804'; } /* '' */ .icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-plus-squared:before { content: '\e806'; } /* '' */ +.icon-retweet:before { content: '\e806'; } /* '' */ .icon-cog:before { content: '\e807'; } /* '' */ .icon-logout:before { content: '\e808'; } /* '' */ .icon-down-open:before { content: '\e809'; } /* '' */ @@ -72,6 +72,8 @@ .icon-lock:before { content: '\e811'; } /* '' */ .icon-globe:before { content: '\e812'; } /* '' */ .icon-brush:before { content: '\e813'; } /* '' */ +.icon-attention:before { content: '\e814'; } /* '' */ +.icon-plus:before { content: '\e815'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ @@ -79,7 +81,9 @@ .icon-menu:before { content: '\f0c9'; } /* '' */ .icon-mail-alt:before { content: '\f0e0'; } /* '' */ .icon-comment-empty:before { content: '\f0e5'; } /* '' */ +.icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ +.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ .icon-binoculars:before { content: '\f1e5'; } /* '' */ .icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/css/fontello-ie7-codes.css b/static/font/css/fontello-ie7-codes.css @@ -3,9 +3,9 @@ .icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); } .icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); } .icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); } -.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); } +.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); } .icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); } -.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); } +.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); } .icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); } .icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); } .icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); } @@ -19,6 +19,8 @@ .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); } .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); } .icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); } +.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); } +.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); } .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); } .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); } .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); } @@ -26,7 +28,9 @@ .icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); } .icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e0;&nbsp;'); } .icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e5;&nbsp;'); } +.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); } .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); } .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); } +.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); } .icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); } .icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf234;&nbsp;'); } \ No newline at end of file diff --git a/static/font/css/fontello-ie7.css b/static/font/css/fontello-ie7.css @@ -14,9 +14,9 @@ .icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); } .icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); } .icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); } -.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); } +.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); } .icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); } -.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); } +.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); } .icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); } .icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); } .icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); } @@ -30,6 +30,8 @@ .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); } .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); } .icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); } +.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); } +.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); } .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); } .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); } .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); } @@ -37,7 +39,9 @@ .icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); } .icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e0;&nbsp;'); } .icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e5;&nbsp;'); } +.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); } .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); } .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); } +.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); } .icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); } .icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf234;&nbsp;'); } \ No newline at end of file diff --git a/static/font/css/fontello.css b/static/font/css/fontello.css @@ -1,11 +1,11 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?60305294'); - src: url('../font/fontello.eot?60305294#iefix') format('embedded-opentype'), - url('../font/fontello.woff2?60305294') format('woff2'), - url('../font/fontello.woff?60305294') format('woff'), - url('../font/fontello.ttf?60305294') format('truetype'), - url('../font/fontello.svg?60305294#fontello') format('svg'); + src: url('../font/fontello.eot?3996201'); + src: url('../font/fontello.eot?3996201#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?3996201') format('woff2'), + url('../font/fontello.woff?3996201') format('woff'), + url('../font/fontello.ttf?3996201') format('truetype'), + url('../font/fontello.svg?3996201#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -15,7 +15,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?60305294#fontello') format('svg'); + src: url('../font/fontello.svg?3996201#fontello') format('svg'); } } */ @@ -59,9 +59,9 @@ .icon-upload:before { content: '\e801'; } /* '' */ .icon-star:before { content: '\e802'; } /* '' */ .icon-star-empty:before { content: '\e803'; } /* '' */ -.icon-retweet:before { content: '\e804'; } /* '' */ +.icon-adjust:before { content: '\e804'; } /* '' */ .icon-eye-off:before { content: '\e805'; } /* '' */ -.icon-plus-squared:before { content: '\e806'; } /* '' */ +.icon-retweet:before { content: '\e806'; } /* '' */ .icon-cog:before { content: '\e807'; } /* '' */ .icon-logout:before { content: '\e808'; } /* '' */ .icon-down-open:before { content: '\e809'; } /* '' */ @@ -75,6 +75,8 @@ .icon-lock:before { content: '\e811'; } /* '' */ .icon-globe:before { content: '\e812'; } /* '' */ .icon-brush:before { content: '\e813'; } /* '' */ +.icon-attention:before { content: '\e814'; } /* '' */ +.icon-plus:before { content: '\e815'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ @@ -82,7 +84,9 @@ .icon-menu:before { content: '\f0c9'; } /* '' */ .icon-mail-alt:before { content: '\f0e0'; } /* '' */ .icon-comment-empty:before { content: '\f0e5'; } /* '' */ +.icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ +.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */ .icon-binoculars:before { content: '\f1e5'; } /* '' */ .icon-user-plus:before { content: '\f234'; } /* '' */ \ No newline at end of file diff --git a/static/font/demo.html b/static/font/demo.html @@ -229,11 +229,11 @@ body { } @font-face { font-family: 'fontello'; - src: url('./font/fontello.eot?32487936'); - src: url('./font/fontello.eot?32487936#iefix') format('embedded-opentype'), - url('./font/fontello.woff?32487936') format('woff'), - url('./font/fontello.ttf?32487936') format('truetype'), - url('./font/fontello.svg?32487936#fontello') format('svg'); + src: url('./font/fontello.eot?15755415'); + src: url('./font/fontello.eot?15755415#iefix') format('embedded-opentype'), + url('./font/fontello.woff?15755415') format('woff'), + url('./font/fontello.ttf?15755415') format('truetype'), + url('./font/fontello.svg?15755415#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -304,9 +304,9 @@ body { <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-star-empty">&#xe803;</i> <span class="i-name">icon-star-empty</span><span class="i-code">0xe803</span></div> </div> <div class="row"> - <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-retweet">&#xe804;</i> <span class="i-name">icon-retweet</span><span class="i-code">0xe804</span></div> + <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-adjust">&#xe804;</i> <span class="i-name">icon-adjust</span><span class="i-code">0xe804</span></div> <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-eye-off">&#xe805;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe805</span></div> - <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-plus-squared">&#xe806;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xe806</span></div> + <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-retweet">&#xe806;</i> <span class="i-name">icon-retweet</span><span class="i-code">0xe806</span></div> <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-cog">&#xe807;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe807</span></div> </div> <div class="row"> @@ -328,19 +328,25 @@ body { <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div> </div> <div class="row"> + <div class="the-icons span3" title="Code: 0xe814"><i class="demo-icon icon-attention">&#xe814;</i> <span class="i-name">icon-attention</span><span class="i-code">0xe814</span></div> + <div class="the-icons span3" title="Code: 0xe815"><i class="demo-icon icon-plus">&#xe815;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe815</span></div> <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div> <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div> - <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> - <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> </div> <div class="row"> + <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> + <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div> <div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div> + </div> + <div class="row"> <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div> + <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div> <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div> + <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> </div> <div class="row"> - <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> + <div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div> <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div> <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div> </div> diff --git a/static/font/font/fontello.eot b/static/font/font/fontello.eot Binary files differ. diff --git a/static/font/font/fontello.svg b/static/font/font/fontello.svg @@ -14,11 +14,11 @@ <glyph glyph-name="star-empty" unicode="&#xe803;" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> -<glyph glyph-name="retweet" unicode="&#xe804;" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> +<glyph glyph-name="adjust" unicode="&#xe804;" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> <glyph glyph-name="eye-off" unicode="&#xe805;" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" /> -<glyph glyph-name="plus-squared" unicode="&#xe806;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> +<glyph glyph-name="retweet" unicode="&#xe806;" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> <glyph glyph-name="cog" unicode="&#xe807;" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" /> @@ -46,6 +46,10 @@ <glyph glyph-name="brush" unicode="&#xe813;" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" /> +<glyph glyph-name="attention" unicode="&#xe814;" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" /> + +<glyph glyph-name="plus" unicode="&#xe815;" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" /> + <glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" /> <glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" /> @@ -60,10 +64,14 @@ <glyph glyph-name="comment-empty" unicode="&#xf0e5;" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" /> +<glyph glyph-name="plus-squared" unicode="&#xf0fe;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + <glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" /> <glyph glyph-name="lock-open-alt" unicode="&#xf13e;" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" /> +<glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" /> + <glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" /> <glyph glyph-name="user-plus" unicode="&#xf234;" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" /> diff --git a/static/font/font/fontello.ttf b/static/font/font/fontello.ttf Binary files differ. diff --git a/static/font/font/fontello.woff b/static/font/font/fontello.woff Binary files differ. diff --git a/static/font/font/fontello.woff2 b/static/font/font/fontello.woff2 Binary files differ. diff --git a/static/styles.json b/static/styles.json @@ -5,5 +5,11 @@ "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" ], - "mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ] + "mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ], + + "redmond-xx": "/static/themes/redmond-xx.json", + "redmond-xx-se": "/static/themes/redmond-xx-se.json", + "redmond-xxi": "/static/themes/redmond-xxi.json", + "breezy-dark": "/static/themes/breezy-dark.json", + "breezy-light": "/static/themes/breezy-light.json" } diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json @@ -0,0 +1,139 @@ +{ + "_pleroma_theme_version": 2, + "name": "Breezy Dark (beta)", + "theme": { + "shadows": { + "panel": [ + { + "x": "1", + "y": "2", + "blur": "6", + "spread": 0, + "color": "#000000", + "alpha": 0.6 + } + ], + "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 + } + ], + "panelHeader": [ + { + "x": 0, + "y": "40", + "blur": "40", + "spread": "-40", + "inset": true, + "color": "#ffffff", + "alpha": "0.1" + } + ], + "buttonHover": [ + { + "x": 0, + "y": "0", + "blur": 0, + "spread": "1", + "color": "--link", + "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 + } + ] + }, + "fonts": {}, + "opacity": { + "input": "1", + "panel": "0" + }, + "colors": { + "bg": "#31363b", + "text": "#eff0f1", + "link": "#3daee9", + "fg": "#31363b", + "panel": "#31363b", + "input": "#232629", + "topBarLink": "#eff0f1", + "btn": "#31363b", + "border": "#4c545b", + "cRed": "#da4453", + "cBlue": "#3daee9", + "cGreen": "#27ae60", + "cOrange": "#f67400" + }, + "radii": { + "btn": "2", + "input": "2", + "checkbox": "1", + "panel": "2", + "avatar": "2", + "avatarAlt": "2", + "tooltip": "2", + "attachment": "2" + } + } +} diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json @@ -0,0 +1,139 @@ +{ + "_pleroma_theme_version": 2, + "name": "Breezy Light (beta)", + "theme": { + "shadows": { + "panel": [ + { + "x": "1", + "y": "2", + "blur": "6", + "spread": 0, + "color": "#000000", + "alpha": 0.6 + } + ], + "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 + } + ], + "panelHeader": [ + { + "x": 0, + "y": "40", + "blur": "40", + "spread": "-40", + "inset": true, + "color": "#ffffff", + "alpha": "0.1" + } + ], + "buttonHover": [ + { + "x": 0, + "y": "0", + "blur": 0, + "spread": "1", + "color": "--link", + "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 + } + ] + }, + "fonts": {}, + "opacity": { + "input": "1" + }, + "colors": { + "bg": "#eff0f1", + "text": "#232627", + "link": "#2980b9", + "fg": "#bcc2c7", + "panel": "#475057", + "panelText": "#fcfcfc", + "input": "#fcfcfc", + "topBar": "#475057", + "topBarLink": "#eff0f1", + "btn": "#eff0f1", + "cRed": "#da4453", + "cBlue": "#2980b9", + "cGreen": "#27ae60", + "cOrange": "#f67400" + }, + "radii": { + "btn": "2", + "input": "2", + "checkbox": "1", + "panel": "2", + "avatar": "2", + "avatarAlt": "2", + "tooltip": "2", + "attachment": "2" + } + } +} diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json @@ -0,0 +1,297 @@ +{ + "_pleroma_theme_version": 2, + "name": "Redmond XX SE", + "theme": { + "shadows": { + "panel": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "panelHeader": [ + { + "x": 0, + "y": 0, + "blur": 0, + "spread": "3", + "inset": true, + "color": "#c0c0c0", + "alpha": 1 + }, + { + "x": "-2200", + "y": 0, + "blur": "200", + "spread": "-2000", + "inset": true, + "color": "#1084d0", + "alpha": 1 + } + ], + "button": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonHover": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonPressed": [ + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "input": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--input", + "alpha": "1", + "inset": true + } + ] + }, + "fonts": {}, + "opacity": { + "input": "1", + "faint": "1" + }, + "colors": { + "bg": "#c0c0c0", + "text": "#000000", + "link": "#0000ff", + "fg": "#c0c0c0", + "panel": "#000080", + "panelFaint": "#c0c0c0", + "input": "#ffffff", + "topBar": "#000080", + "topBarLink": "#ffffff", + "btn": "#c0c0c0", + "faint": "#3f3f3f", + "faintLink": "#404080", + "border": "#808080", + "cRed": "#FF0000", + "cBlue": "#008080", + "cGreen": "#008000", + "cOrange": "#808000" + }, + "radii": { + "btn": "0", + "input": "0", + "checkbox": "0", + "panel": "0", + "avatar": "0", + "avatarAlt": "0", + "tooltip": "0", + "attachment": "0" + } + } +} diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json @@ -0,0 +1,288 @@ +{ + "_pleroma_theme_version": 2, + "name": "Redmond XX", + "theme": { + "shadows": { + "panel": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "panelHeader": [ + { + "x": 0, + "y": 0, + "blur": 0, + "spread": "3", + "inset": true, + "color": "#c0c0c0", + "alpha": 1 + } + ], + "button": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonHover": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonPressed": [ + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "input": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#000000", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--input", + "alpha": "1", + "inset": true + } + ] + }, + "fonts": {}, + "opacity": { + "input": "1", + "faint": "1" + }, + "colors": { + "bg": "#c0c0c0", + "text": "#000000", + "link": "#0000ff", + "fg": "#c0c0c0", + "panel": "#000080", + "panelFaint": "#c0c0c0", + "input": "#ffffff", + "topBar": "#000080", + "topBarLink": "#ffffff", + "btn": "#c0c0c0", + "faint": "#3f3f3f", + "faintLink": "#404080", + "border": "#808080", + "cRed": "#FF0000", + "cBlue": "#008080", + "cGreen": "#008000", + "cOrange": "#808000" + }, + "radii": { + "btn": "0", + "input": "0", + "checkbox": "0", + "panel": "0", + "avatar": "0", + "avatarAlt": "0", + "tooltip": "0", + "attachment": "0" + } + } +} diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json @@ -0,0 +1,270 @@ +{ + "_pleroma_theme_version": 2, + "name": "Redmond XXI", + "theme": { + "shadows": { + "panel": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#404040", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#dfdfdf", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "panelHeader": [ + { + "x": 0, + "y": 0, + "blur": 0, + "spread": "3", + "inset": true, + "color": "#d6d6ce", + "alpha": 1 + }, + { + "x": "-2200", + "y": 0, + "blur": "200", + "spread": "-2000", + "inset": true, + "color": "#a5cef7", + "alpha": 1 + } + ], + "button": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#404040", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonHover": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#404040", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "buttonPressed": [ + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#404040", + "alpha": "1", + "inset": true + }, + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--bg", + "alpha": "1", + "inset": true + } + ], + "input": [ + { + "x": "-1", + "y": "-1", + "blur": "0", + "spread": 0, + "color": "#FFFFFF", + "alpha": "1", + "inset": true + }, + { + "x": "1", + "y": "1", + "blur": "0", + "spread": 0, + "color": "#848484", + "alpha": "1", + "inset": true + }, + { + "x": "-2", + "y": "-2", + "blur": "0", + "spread": 0, + "color": "#d4d0c8", + "alpha": "1", + "inset": true + }, + { + "x": "2", + "y": "2", + "blur": "0", + "spread": 0, + "color": "#404040", + "alpha": "1", + "inset": true + }, + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "3", + "color": "--input", + "alpha": "1", + "inset": true + } + ] + }, + "fonts": {}, + "opacity": { + "input": "1", + "faint": "1" + }, + "colors": { + "bg": "#d6d6ce", + "text": "#000000", + "link": "#0000ff", + "fg": "#d6d6ce", + "panel": "#042967", + "panelFaint": "#FFFFFF", + "input": "#ffffff", + "topBar": "#042967", + "topBarLink": "#ffffff", + "btn": "#d6d6ce", + "faint": "#3f3f3f", + "faintLink": "#404080", + "border": "#808080", + "cRed": "#c42726", + "cBlue": "#6699cc", + "cGreen": "#669966", + "cOrange": "#cc6633" + }, + "radii": { + "btn": "0", + "input": "0", + "checkbox": "0", + "panel": "0", + "avatar": "0", + "avatarAlt": "0", + "tooltip": "0", + "attachment": "0" + } + } +} diff --git a/yarn.lock b/yarn.lock @@ -1205,6 +1205,10 @@ chokidar@^1.0.0, chokidar@^1.4.1: optionalDependencies: fsevents "^1.0.0" +chromatism@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chromatism/-/chromatism-3.0.0.tgz#a7249d353c1e4f3577e444ac41171c4e2e624b12" + chromedriver@^2.21.2: version "2.35.0" resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.35.0.tgz#c103ba2fb3d1671f666058159f5cbaa816902e4d"