From e7fe2dc9f9e52771c2ffe8d0ee1c4e8b2e38ab2f Mon Sep 17 00:00:00 2001 From: Henry Jameson <me@hjkos.com> Date: Wed, 14 Nov 2018 19:39:17 +0300 Subject: [PATCH] collateral fixes, removed alpha control for alerts, added contrast text generation for alerts, updated getTextColor to also have fallback to black/white if resulting contrast isn't passable (only when inverting lightness!), updated UI to use tabs. --- src/App.js | 7 +- src/App.scss | 9 +- .../contrast_ratio/contrast_ratio.vue | 4 +- .../delete_button/delete_button.vue | 2 +- .../notifications/notifications.scss | 10 +- .../style_switcher/style_switcher.js | 40 +-- .../style_switcher/style_switcher.vue | 257 +++++++++--------- src/components/timeline/timeline.vue | 5 - src/services/color_convert/color_convert.js | 23 +- src/services/style_setter/style_setter.js | 28 +- .../user_highlighter/user_highlighter.js | 2 +- 11 files changed, 200 insertions(+), 187 deletions(-) diff --git a/src/App.js b/src/App.js index 05e3eda3..b06e8b5d 100644 --- 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 index 0a2ff5cc..8fb3c488 100644 --- a/src/App.scss +++ b/src/App.scss @@ -473,14 +473,19 @@ nav { 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--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); + } } } diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index a428e75f..cb65c371 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -1,5 +1,5 @@ <template> -<span class="contrast-ratio"> +<span v-if="contrast" class="contrast-ratio"> <span :title="`Contrast is ${contrast.text}`" class="rating"> <span v-if="contrast.aaa"> <i class="icon-thumbs-up-alt"/> @@ -11,7 +11,7 @@ <i class="icon-attention"/> </span> </span> - <span class="rating" v-if="large" :title="`Contrast is ${contrast.text} (18pt+)`"> + <span class="rating" v-if="contrast && large" :title="`Contrast is ${contrast.text} (18pt+)`"> <span v-if="contrast.aaa"> <i class="icon-thumbs-up-alt"/> </span> diff --git a/src/components/delete_button/delete_button.vue b/src/components/delete_button/delete_button.vue index d13547e2..b458b0dc 100644 --- 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/notifications/notifications.scss b/src/components/notifications/notifications.scss index 98fdd3f5..fcc0c3d4 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -8,13 +8,13 @@ display: inline-block; background-color: $fallback--cRed; background-color: var(--badgeNotification, $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; + color: var(--badgeNotificationText, white); font-size: 15px; line-height: 22px; text-align: center; @@ -27,8 +27,8 @@ } .unseen { - box-shadow: inset 4px 0 0 var(--badgeNotification, $fallback--cRed); - padding-left: 0; + + background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px) } } @@ -42,8 +42,8 @@ .broken-favorite { border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - color: $fallback--faint; - color: var(--faint, $fallback--faint); + color: $fallback--text; + color: var(--alertErrorText, $fallback--text); background-color: $fallback--alertError; background-color: var(--alertError, $fallback--alertError); padding: 2px .5em diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 8953dc49..9e236488 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -1,8 +1,9 @@ -import { rgb2hex, hex2rgb, getContrastRatio, worstCase } from '../../services/color_convert/color_convert.js' +import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js' import ColorInput from '../color_input/color_input.vue' import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' import OpacityInput from '../opacity_input/opacity_input.vue' import StyleSetter from '../../services/style_setter/style_setter.js' +import TabSwitcher from '../tab_switcher/tab_switcher.jsx' export default { data () { @@ -38,7 +39,6 @@ export default { topBarTextColorLocal: undefined, topBarLinkColorLocal: undefined, - alertOpacityLocal: undefined, alertErrorColorLocal: undefined, badgeOpacityLocal: undefined, @@ -123,8 +123,6 @@ export default { btn: this.btnOpacityLocal, input: this.inputOpacityLocal, panel: this.panelOpacityLocal, - alert: this.alertOpacityLocal, - badge: this.badgeOpacityLocal, topBar: this.topBarOpacityLocal, border: this.borderOpacityLocal, faint: this.faintOpacityLocal @@ -160,6 +158,7 @@ export default { if (!this.previewTheme.colors) 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 @@ -197,26 +196,28 @@ export default { 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(worstCase(bgs.bg, opacity.bg, fgs.text), fgs.text), - bgLink: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.link), fgs.link), - bgRed: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.red), fgs.red), - bgGreen: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.green), fgs.green), - bgBlue: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.blue), fgs.blue), - bgOrange: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.orange), fgs.orange), + 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(worstCase(bgs.bg, 0.5, fgs.panelText), fgs.text), + tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text), - panelText: getContrastRatio(worstCase(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText), + panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText), - btnText: getContrastRatio(worstCase(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText), + btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText), - inputText: getContrastRatio(worstCase(bgs.input, opacity.input, fgs.inputText), fgs.inputText), + inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText), - badgeNotification: getContrastRatio(worstCase(bgs.badgeNotification, opacity.badge, fgs.text), fgs.text), - - topBarText: getContrastRatio(worstCase(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText), - topBarLink: getContrastRatio(worstCase(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink) + 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 }, {}) @@ -229,7 +230,8 @@ export default { components: { ColorInput, OpacityInput, - ContrastRatio + ContrastRatio, + TabSwitcher }, methods: { exportCurrentTheme () { diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index f4b4e88f..5bc38318 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -24,8 +24,8 @@ </div> </div> - <div class="preview-container" :style="previewRules"> - <div class="panel dummy"> + <div class="preview-container"> + <div class="panel dummy" :style="previewRules"> <div class="panel-heading">Preview</div> <div class="panel-body theme-preview-content"> <div class="avatar"> @@ -45,132 +45,132 @@ </div> </div> - <div class="color-container"> <p>{{$t('settings.theme_help')}}</p> - <h3>Basic colors!!</h3> - <div> - <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"/> + <tab-switcher> + <div label="Basic" class="color-container"> + <div class="color-item"> + <h4>Main colors</h4> + <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"> + <h4>Panel header, top bar, buttons, text fields</h4> + <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>See "Advanced" tab for more detailed control</p> + </div> + <div class="color-item"> + <h4>Icons, alerts, etc.</h4> + <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"> + <h4>.</h4> + <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> </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"/> - </div> - <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> - </div> - <h3>More customs!</h3> - <div> - <div class="color-item"> - <h4>Alerts</h4> - <ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.error')" :fallback="previewTheme.colors.alertError"/> - <OpacityInput name="alertOpacity" v-model="alertOpacityLocal" :fallback="previewTheme.opacity.alert || 1"/> + <div label="Advanced" class="color-container"> + <div class="color-item"> + <h4>Alerts</h4> + <ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.error')" :fallback="previewTheme.colors.alertError"/> + <ContrastRatio :contrast="previewContrast.alertError"/> + </div> + <div class="color-item"> + <h4>Badges</h4> + <ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.notification')" :fallback="previewTheme.colors.badgeNotification"/> + </div> + <div class="color-item"> + <h4>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.links')"/> + <ContrastRatio :contrast="previewContrast.panelText" large="1"/> + </div> + <div class="color-item"> + <h4>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>Text fields</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>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>Borders</h4> + <ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" label="Color"/> + <OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/> + </div> + <div class="color-item"> + <h4>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.panel')"/> + <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/> + </div> </div> - <div class="color-item"> - <h4>Alerts</h4> - <ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.notification')" :fallback="previewTheme.colors.badgeNotification"/> - <ContrastRatio :contrast="previewContrast.badgeNotification"/> - <OpacityInput name="badgeOpacity" v-model="badgeOpacityLocal" :fallback="previewTheme.opacity.badge || 1"/> + <div label="Roundness" 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 class="color-item"> - <h4>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.links')"/> - <ContrastRatio :contrast="previewContrast.panelText" large="1"/> - </div> - <div class="color-item"> - <h4>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>Text fields</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>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>Borders</h4> - <ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" label="Color"/> - <OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/> - </div> - <div class="color-item"> - <h4>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.panel')"/> - <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/> - </div> - </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> + </tab-switcher> <div class="apply-container"> <button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button> @@ -193,14 +193,12 @@ .apply-container, .radius-container, -.color-container > div, +.color-container, .presets-container { display: flex; p { - flex: 2 0 100%; - margin-top: 2em; - margin-bottom: .5em; + margin-left: 1em } } @@ -208,7 +206,7 @@ flex-direction: column; } -.color-container > div{ +.color-container{ flex-wrap: wrap; justify-content: space-between; } @@ -231,6 +229,9 @@ border-color: var(--border, $fallback--border); margin: 1em -1em 0; padding: 1em; + background: var(--body-background-image); + background-size: cover; + background-position: 50% 50%; .btn { margin-top: 1em; diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 77a9a2af..c2d5b9e6 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -63,11 +63,6 @@ color: $fallback--faint; color: var(--panelFaint, $fallback--faint); } - - .loadmore-error { - color: $fallback--text; - color: var(--text, $fallback--text); - } } .new-status-notification { diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 7a5254b9..6f046a1d 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -80,30 +80,23 @@ const getContrastRatio = (a, b) => { } /** - * This generates what "worst case" color would look like for transparent - * segments. I.e. transparent black with yellow text over yellow background. + * This performs alpha blending between solid background and semi-transparent foreground * - * @param {Object} srgb - transparent color - * @param {Number} alpha - color's opacity/alpha channel - * @param {Object} textSrgb - text color (considered as worst case scenario for transparent background) + * @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 transparentWorstCase = (srgb, alpha, textSrgb) => { +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] = (srgb[c] * alpha + textSrgb[c] * (1 - alpha)) + acc[c] = (fg[c] * fga + bg[c] * (1 - fga)) return acc }, {}) } -const worstCase = (bg, bga, text) => { - console.log(bg) - console.log(text) - if (bga === 1 || typeof bga === 'undefined') return bg - return transparentWorstCase(bg, bga, text) -} - const hex2rgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? { @@ -134,5 +127,5 @@ export { mixrgb, rgbstr2hex, getContrastRatio, - worstCase + alphaBlend } diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 666e74c1..9388e6f9 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,6 +1,6 @@ import { times } from 'lodash' -import { brightness, invertLightness, convert } from 'chromatism' -import { rgb2hex, hex2rgb, mixrgb } 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 @@ -58,13 +58,17 @@ const rgb2rgba = function (rgba) { return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})` } -const getTextColor = function (bg, text) { +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 } : {} - return Object.assign(base, invertLightness(text).rgb) + const result = Object.assign(base, invertLightness(text).rgb) + if (!preserve && getContrastRatio(bg, result) < 4.5) { + return contrastRatio(bg, text).rgb + } + return result } return text } @@ -104,7 +108,12 @@ const generatePreset = (input) => { alert: 0.5, input: 0.5, faint: 0.5 - }, input.opacity) + }, 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') { @@ -128,9 +137,9 @@ const generatePreset = (input) => { colors.fg = col.fg colors.fgText = col.fgText || getTextColor(colors.fg, colors.text) - colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link) + colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true) - colors.border = col.border || brightness(20 * mod, colors.fg).rgb + 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) @@ -156,8 +165,11 @@ const generatePreset = (input) => { colors.cOrange = col.cOrange colors.alertError = col.alertError || Object.assign({}, col.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({}, col.cRed) - colors.badgeNotificationText = col.badgeNotification || Object.assign({}, col.cRed) + colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb Object.entries(opacity).forEach(([ k, v ]) => { if (typeof v === 'undefined') return diff --git a/src/services/user_highlighter/user_highlighter.js b/src/services/user_highlighter/user_highlighter.js index ebb25eca..f6ddfb9c 100644 --- 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,`,