From 4072f3c82a7ff1d016e9a1fae92d6a7e12778a14 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Fri, 28 Aug 2015 22:54:12 -0400 Subject: [PATCH] 3.5.17 to 3.5.21. Added font family option. Added timestamp seconds option. Fixed regex emoticons in My Emoticons pasting the wrong code. Added Google Noto emoji. Fixed broken chat line alternation. Made My Emoticons menu button not visible when the menu wouldn't contain anything. --- gulpfile.js | 9 ++++- src/badges.js | 95 ++++++++++++++++++++++++++++++++++++++------- src/ember/line.js | 90 +++++++++++++++++++++++++++++++----------- src/ember/room.js | 34 ++++++++++------ src/emoticons.js | 23 ++++++----- src/main.js | 2 +- src/socket.js | 2 +- src/tokenize.js | 33 ++++++++++++---- src/ui/my_emotes.js | 44 ++++++++++++--------- style.css | 26 ++++++++++++- 10 files changed, 270 insertions(+), 88 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 13128efa..baa2333e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -105,15 +105,20 @@ gulp.task('server', function() { return request.get("http://cdn.frankerfacez.com/" + uri).pipe(res); } + var headers = {"Access-Control-Allow-Origin": "*"}; + if ( fs.lstatSync(file).isDirectory() ) { util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.red("403") + " GET " + util.colors.magenta(uri)); - res.writeHead(403, {"Access-Control-Allow-Origin": "*"}); + res.writeHead(403, headers); res.write('403 Forbidden'); return res.end(); } + if ( file.substr(file.length-4) === ".svg" ) + headers['Content-Type'] = 'image/svg+xml'; + util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.green("200") + " GET " + util.colors.magenta(uri)); - res.writeHead(200, {"Access-Control-Allow-Origin": "*"}); + res.writeHead(200, headers); fs.createReadStream(file).pipe(res); }); diff --git a/src/badges.js b/src/badges.js index ce80ada2..f418153c 100644 --- a/src/badges.js +++ b/src/badges.js @@ -17,19 +17,77 @@ FFZ.settings_info.show_badges = { }; +FFZ.settings_info.legacy_badges = { + type: "select", + options: { + 0: "Default", + 1: "Moderator Only", + 2: "Mod + Turbo", + 3: "All Legacy Badges" + }, + value: 0, + + category: "Chat Appearance", + + name: "Legacy Badges", + help: "Use the old, pre-vector chat badges from Twitch in place of the new.", + + process_value: function(val) { + if ( val === false ) + return 0; + else if ( val === true ) + return 3; + else if ( typeof val === "string" ) + return parseInt(val || "0"); + return val; + }, + + on_update: function(val) { + document.body.classList.toggle("ffz-legacy-mod-badges", val !== 0); + document.body.classList.toggle("ffz-legacy-turbo-badges", val > 1); + document.body.classList.toggle("ffz-legacy-badges", val === 3); + } + }; + + FFZ.settings_info.transparent_badges = { - type: "boolean", - value: false, + type: "select", + options: { + 0: "Default", + 1: "Rounded", + 2: "Circular", + 3: "Circular (Color Only)", + 4: "Circular (Color Only, Small)", + 5: "Transparent" + }, + + value: 0, category: "Chat Appearance", no_bttv: true, - - name: "Transparent Badges", - help: "Make chat badges transparent for a nice, clean look. On light chat, non-subscriber badges are inverted to remain visible.", - + + name: "Badge Style", + help: "Make badges appear rounded, completely circular, or transparent with no background at all.", + + process_value: function(val) { + if ( val === false ) + return 0; + else if ( val === true ) + return 5; + else if ( typeof val === "string" ) + return parseInt(val || "0"); + return val; + }, + on_update: function(val) { - if ( ! this.has_bttv ) - document.body.classList.toggle("ffz-transparent-badges", val); + if ( this.has_bttv ) + return; + + document.body.classList.toggle("ffz-rounded-badges", val === 1); + document.body.classList.toggle("ffz-circular-badges", val === 2); + document.body.classList.toggle("ffz-circular-blank-badges", val === 3); + document.body.classList.toggle("ffz-circular-small-badges", val === 4); + document.body.classList.toggle("ffz-transparent-badges", val === 5); } }; @@ -39,9 +97,18 @@ FFZ.settings_info.transparent_badges = { // -------------------- FFZ.prototype.setup_badges = function() { - if ( ! this.has_bttv ) - document.body.classList.toggle("ffz-transparent-badges", this.settings.transparent_badges); - + if ( ! this.has_bttv ) { + document.body.classList.toggle("ffz-rounded-badges", this.settings.transparent_badges === 1); + document.body.classList.toggle("ffz-circular-badges", this.settings.transparent_badges === 2); + document.body.classList.toggle("ffz-circular-blank-badges", this.settings.transparent_badges === 3); + document.body.classList.toggle("ffz-circular-small-badges", this.settings.transparent_badges === 4); + document.body.classList.toggle("ffz-transparent-badges", this.settings.transparent_badges === 5); + } + + document.body.classList.toggle("ffz-legacy-mod-badges", this.settings.legacy_badges !== 0); + document.body.classList.toggle("ffz-legacy-turbo-badges", this.settings.legacy_badges > 1); + document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges === 3); + this.log("Preparing badge system."); this.badges = {}; @@ -206,7 +273,7 @@ FFZ.prototype.render_badges = function(component, badges) { var visible = full_badge.visible; if ( typeof visible === "function" ) visible = visible.bind(this)(room_id, user, component, badges); - + if ( ! visible ) continue; } @@ -215,7 +282,7 @@ FFZ.prototype.render_badges = function(component, badges) { var replaces = badge.hasOwnProperty('replaces') ? badge.replaces : full_badge.replaces; if ( ! replaces ) continue; - + old_badge.image = badge.image || full_badge.image; old_badge.klass += ' ffz-badge-replacement'; old_badge.title += ', ' + (badge.title || full_badge.title); @@ -230,7 +297,7 @@ FFZ.prototype.render_badges = function(component, badges) { extra_css: badge.extra_css }; } - + return badges; } diff --git a/src/ember/line.js b/src/ember/line.js index 0e55d8b1..3c660534 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -59,13 +59,29 @@ FFZ.settings_info.replace_bad_emotes = { FFZ.settings_info.parse_emoji = { - type: "boolean", - value: true, + type: "select", + options: { + 0: "No Images / Font Only", + 1: "Twitter Emoji Images", + 2: "Google Noto Images" + }, + + value: 1, + + process_value: function(val) { + if ( val === false ) + return 0; + if ( val === true ) + return 1; + if ( typeof val === "string" ) + return parseInt(val || "0"); + return val; + }, category: "Chat Appearance", - name: "Replace Emoji with Images", - help: "Replace emoji in chat messages with nicer looking images from the open-source Twitter Emoji project." + name: "Emoji Display", + help: "Replace emoji in chat messages with nicer looking images from either Twitter or Google." }; @@ -262,19 +278,6 @@ FFZ.settings_info.image_hover_all_domains = { }; -FFZ.settings_info.legacy_badges = { - type: "boolean", - value: false, - - category: "Chat Appearance", - - name: "Legacy Badges", - help: "Display the old, pre-vector chat badges from Twitch.", - - on_update: function(val) { document.body.classList.toggle("ffz-legacy-badges", val); } - }; - - FFZ.settings_info.chat_rows = { type: "boolean", value: false, @@ -369,6 +372,52 @@ FFZ.settings_info.high_contrast_chat = { }; +FFZ.settings_info.chat_font_family = { + type: "button", + value: null, + + category: "Chat Appearance", + no_bttv: true, + + name: "Font Family", + help: "Change the font used for rendering chat messages.", + + method: function() { + var old_val = this.settings.chat_font_family || "", + new_val = prompt("Chat Font Family\n\nPlease enter a font family to use rendering chat. Leave this blank to use the default.", old_val); + + if ( new_val === null || new_val === undefined ) + return; + + // Should we wrap this with quotes? + if ( ! new_val ) + new_val = null; + + this.settings.set("chat_font_family", new_val); + }, + + on_update: function(val) { + if ( this.has_bttv || ! this._chat_style ) + return; + + var css; + if ( ! val ) + css = ""; + else { + // Let's escape this to avoid goofing anything up if there's bad user input. + if ( val.indexOf(' ') !== -1 && val.indexOf(',') === -1 && val.indexOf('"') === -1 && val.indexOf("'") === -1) + val = '"' + val + '"'; + + var span = document.createElement('span'); + span.style.fontFamily = val; + css = ".ember-chat .chat-messages {" + span.style.cssText + "}"; + } + + utils.update_css(this._chat_style, "chat_font_family", css); + } + }; + + FFZ.settings_info.chat_font_size = { type: "button", value: 12, @@ -486,13 +535,13 @@ FFZ.prototype.setup_line = function() { // Initial calculation. FFZ.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size); + FFZ.settings_info.chat_font_family.on_update.bind(this)(this.settings.chat_font_family); // Chat Enhancements document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color !== '-1'); document.body.classList.toggle("ffz-chat-colors-gray", !this.has_bttv && this.settings.fix_color === '-1'); - document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges); document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows); document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && this.settings.chat_separators !== '0'); document.body.classList.toggle("ffz-chat-separator-3d", !this.has_bttv && this.settings.chat_separators === '2'); @@ -652,16 +701,11 @@ FFZ.prototype._modify_line = function(component) { this_ul = this.get('ffzUserLevel'), other_ul = room && room.room && room.room.get('ffzUserLevel') || 0, - row_type = this.get('msgObject.ffz_alternate'), raw_color = this.get('msgObject.color'), colors = raw_color && f._handle_color(raw_color), is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')); - if ( row_type === undefined ) { - row_type = f._last_row[room_id] = f._last_row.hasOwnProperty(room_id) ? !f._last_row[room_id] : false; - this.set("msgObject.ffz_alternate", row_type); - } e.push('
'); e.push('' + this.get("timestamp") + ' '); diff --git a/src/ember/room.js b/src/ember/room.js index 39341dad..9f06fa6a 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -941,7 +941,7 @@ FFZ.prototype._modify_room = function(room) { if ( msg.from === user ) { if ( f.settings.remove_deleted ) { if ( alternate === undefined ) - alternate = msg.ffz_alternate; + alternate = ! msg.ffz_alternate; msgs.removeAt(i); continue; } @@ -1032,6 +1032,13 @@ FFZ.prototype._modify_room = function(room) { ffzActualPushMessage: function (msg) { if ( this.shouldShowMessage(msg) && this.ffzShouldShowMessage(msg) ) { + var row_type = msg.ffz_alternate; + if ( row_type === undefined ) { + var room_id = this.get('id'); + row_type = f._last_row[room_id] = f._last_row.hasOwnProperty(room_id) ? !f._last_row[room_id] : false; + msg.ffz_alternate = row_type; + } + this.get("messages").pushObject(msg); this.trimMessages(); @@ -1233,16 +1240,21 @@ FFZ.prototype._modify_room = function(room) { this.tmiRoom.list().done(function(data) { var chatters = {}; data = data.data.chatters; - for(var i=0; i < data.admins.length; i++) - chatters[data.admins[i]] = true; - for(var i=0; i < data.global_mods.length; i++) - chatters[data.global_mods[i]] = true; - for(var i=0; i < data.moderators.length; i++) - chatters[data.moderators[i]] = true; - for(var i=0; i < data.staff.length; i++) - chatters[data.staff[i]] = true; - for(var i=0; i < data.viewers.length; i++) - chatters[data.viewers[i]] = true; + if ( data && data.admins ) + for(var i=0; i < data.admins.length; i++) + chatters[data.admins[i]] = true; + if ( data && data.global_mods ) + for(var i=0; i < data.global_mods.length; i++) + chatters[data.global_mods[i]] = true; + if ( data && data.moderators ) + for(var i=0; i < data.moderators.length; i++) + chatters[data.moderators[i]] = true; + if ( data && data.staff ) + for(var i=0; i < data.staff.length; i++) + chatters[data.staff[i]] = true; + if ( data && data.viewers ) + for(var i=0; i < data.viewers.length; i++) + chatters[data.viewers[i]] = true; room.set("ffz_chatters", chatters); room.ffzUpdateChatters(); diff --git a/src/emoticons.js b/src/emoticons.js index 4b44c926..d22b3f24 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -218,7 +218,7 @@ FFZ.prototype._emote_tooltip = function(emote) { FFZ.prototype.load_emoji_data = function(callback, tries) { var f = this; - jQuery.getJSON(constants.SERVER + "emoji/emoji.json") + jQuery.getJSON(constants.SERVER + "emoji/emoji-data.json") .done(function(data) { var new_data = {}, by_name = {}; @@ -226,27 +226,32 @@ FFZ.prototype.load_emoji_data = function(callback, tries) { var emoji = data[eid]; eid = eid.toLowerCase(); emoji.code = eid; - + new_data[eid] = emoji; by_name[emoji.short_name] = eid; emoji.raw = _.map(emoji.code.split("-"), from_code_point).join(""); - emoji.src = constants.SERVER + 'emoji/' + eid + '-1x.png'; - emoji.srcSet = emoji.src + ' 1x, ' + constants.SERVER + 'emoji/' + eid + '-2x.png 2x, ' + constants.SERVER + 'emoji/' + eid + '-4x.png 4x'; + emoji.tw_src = constants.SERVER + 'emoji/tw-' + eid + '.svg'; + emoji.noto_src = constants.SERVER + 'emoji/noto-' + eid + '.svg'; emoji.token = { - srcSet: emoji.srcSet, - emoticonSrc: emoji.src, + emoticonSrc: true, + + tw_src: emoji.tw_src, + noto_src: emoji.noto_src, + + tw: emoji.tw, + noto: emoji.noto, + ffzEmoji: eid, altText: emoji.raw - }; - + }; } f.emoji_data = new_data; f.emoji_names = by_name; - + f.log("Loaded data on " + Object.keys(new_data).length + " emoji."); if ( typeof callback === "function" ) callback(true, data); diff --git a/src/main.js b/src/main.js index e66e5a57..159cc75a 100644 --- a/src/main.js +++ b/src/main.js @@ -21,7 +21,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 16, + major: 3, minor: 5, revision: 21, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } diff --git a/src/socket.js b/src/socket.js index 43c55ca8..6263c280 100644 --- a/src/socket.js +++ b/src/socket.js @@ -176,7 +176,7 @@ FFZ.prototype.ws_create = function() { } catch(err) { f.error("Callback for " + request + ": " + err); } - + f._ws_callbacks[request] = undefined; } } diff --git a/src/tokenize.js b/src/tokenize.js index d6130513..0e5a4ce9 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -258,6 +258,18 @@ FFZ.settings_info.twenty_four_timestamps = { }; +FFZ.settings_info.timestamp_seconds = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "Timestamp Seconds", + help: "Display seconds in chat timestamps." + }; + + FFZ.settings_info.show_deleted_links = { type: "boolean", value: false, @@ -297,14 +309,15 @@ FFZ.prototype.setup_tokenization = function() { return '?:??'; var hours = e.getHours(), - minutes = e.getMinutes(); + minutes = e.getMinutes(), + seconds = e.getSeconds(); if ( hours > 12 && ! f.settings.twenty_four_timestamps ) hours -= 12; else if ( hours === 0 && ! f.settings.twenty_four_timestamps ) hours = 12; - return hours + ':' + (minutes < 10 ? '0' : '') + minutes; + return hours + ':' + (minutes < 10 ? '0' : '') + minutes + (f.settings.timestamp_seconds ? ':' + (seconds < 10 ? '0' : '') + seconds : ''); }; @@ -523,7 +536,7 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { var f = this; return _.map(tokens, function(token) { if ( token.emoticonSrc ) { - var tooltip, srcset, extra; + var tooltip, src = token.emoticonSrc, srcset, extra; if ( token.ffzEmote ) { var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet], emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote]; @@ -534,11 +547,15 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { } else if ( token.ffzEmoji ) { var eid = token.ffzEmoji, - emoji = f.emoji_data && f.emoji_data[eid]; + emoji = f.emoji_data && f.emoji_data[eid], + setting = f.settings.parse_emoji; - tooltip = emoji ? "Emoji: " + token.altText + "\nName: :" + emoji.short_name + ":" : token.altText; - srcset = emoji ? emoji.srcSet : token.srcSet; - extra = ' data-ffz-emoji="' + eid + '"'; + if ( setting === 0 || (setting === 1 && ! emoji.tw) || (setting === 2 && ! emoji.noto) ) + return token.altText; + + tooltip = emoji ? "Emoji: " + token.altText + "\nName: " + emoji.name + (emoji.short_name ? "\nShort Name: :" + emoji.short_name + ":" : "") : token.altText; + extra = ' data-ffz-emoji="' + eid + '" height="18px"'; + src = setting === 2 ? token.noto_src : token.tw_src; } else { var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc), @@ -571,7 +588,7 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { srcset = build_srcset(id); } - return ''; + return ''; } if ( token.isLink ) { diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index 802ac9ac..8606cffe 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -91,9 +91,14 @@ FFZ.menu_pages.myemotes = { var user = this.get_user(), tmi = view.get('controller.currentRoom.tmiSession'), ffz_sets = user && this.users[user.login] && this.users[user.login].sets || [], - twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets']; + twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets'], - return ffz_sets.length || (twitch_sets && Object.keys(twitch_sets).length); + sk = twitch_sets && Object.keys(twitch_sets); + + if ( sk && ! this.settings.global_emotes_in_menu && sk.indexOf('0') !== -1 ) + sk.removeObject('0'); + + return ffz_sets.length || (sk && sk.length) || this.settings.emoji_in_menu; }, render: function(view, container) { @@ -122,11 +127,14 @@ FFZ.menu_pages.myemotes = { draw_emoji: function(view) { var heading = document.createElement('div'), menu = document.createElement('div'), - f = this; + f = this, + settings = this.settings.parse_emoji || 1; + heading.className = 'heading'; - heading.innerHTML = 'FrankerFaceZEmoji'; - heading.style.backgroundImage = 'url("' + constants.SERVER + '/emoji/1f4af-1x.png")'; + heading.innerHTML = 'UnicodeEmoji'; + heading.style.backgroundImage = 'url("' + constants.SERVER + 'emoji/' + (settings === 2 ? 'noto-' : 'tw-') + '1f4af.svg")'; + heading.style.backgroundSize = "18px"; menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); @@ -136,12 +144,13 @@ FFZ.menu_pages.myemotes = { heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); var set = []; + for(var eid in this.emoji_data) set.push(this.emoji_data[eid]); set.sort(function(a,b) { - var an = a.short_name.toLowerCase(), - bn = b.short_name.toLowerCase(); + var an = (a.name || "").toLowerCase(), + bn = (b.name || "").toLowerCase(); if ( an < bn ) return -1; else if ( an > bn ) return 1; @@ -152,18 +161,17 @@ FFZ.menu_pages.myemotes = { for(var i=0; i < set.length; i++) { var emoji = set[i], - em = document.createElement('span'), - img_set = 'image-set(url("' + emoji.src + '") 1x, url("' + constants.SERVER + 'emoji/' + emoji.code + '-2x.png") 2x, url("' + constants.SERVER + 'emoji/' + emoji.code + '-4x.png") 4x)'; + em = document.createElement('span'); + + if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) ) + continue; em.className = 'emoticon tooltip'; - em.title = 'Emoji: ' + emoji.raw + '\nName: :' + emoji.short_name + ':'; + em.title = 'Emoji: ' + emoji.raw + '\nName: ' + emoji.name + (emoji.short_name ? '\nShort Name: :' + emoji.short_name + ':' : ''); em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw)); - em.style.backgroundImage = 'url("' + emoji.src + '")'; - em.style.backgroundImage = '-webkit-' + img_set; - em.style.backgroundImage = '-moz-' + img_set; - em.style.backgroundImage = '-ms-' + img_set; - em.style.backgroudnImage = img_set; + em.style.backgroundImage = 'url("' + (settings === 2 ? emoji.noto_src : emoji.tw_src) + '")'; + em.style.backgroundSize = "18px"; menu.appendChild(em); } @@ -244,13 +252,13 @@ FFZ.menu_pages.myemotes = { } em.title = code; - em.addEventListener("click", function(id, code, e) { + em.addEventListener("click", function(id, c, e) { e.preventDefault(); if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) window.open("https://twitchemotes.com/emote/" + id); else - this._add_emote(view, code); - }.bind(this, emote.id, emote.code)); + this._add_emote(view, c); + }.bind(this, emote.id, code)); menu.appendChild(em); } diff --git a/style.css b/style.css index d415c3ad..1cd8d82d 100644 --- a/style.css +++ b/style.css @@ -1729,7 +1729,29 @@ body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { border-right-color: transparent; } -/* Hidden Badges */ +/* Badge Styles */ + +.ffz-rounded-badges .ember-chat .badges .badge:not(.subscriber) { border-radius: 2px; } + +.ffz-circular-blank-badges .ember-chat .badges .badge:not(.subscriber), +.ffz-circular-small-badges .ember-chat .badges .badge:not(.subscriber), +.ffz-circular-badges .ember-chat .badges .badge:not(.subscriber) { + border-radius: 9px; + background-size: 16px; + background-repeat: no-repeat; + background-position: center center; +} + +.ffz-circular-small-badges .ember-chat .badges .badge:not(.subscriber), +.ffz-circular-blank-badges .ember-chat .badges .badge:not(.subscriber) { + background-size: 0px; +} + +.ffz-circular-small-badges .ember-chat .badges .badge:not(.subscriber) { + height: 10px; + min-width: 10px; + margin: 5px 3px 5px 0; +} .ffz-transparent-badges .ember-chat .badges .badge { background-color: transparent !important; @@ -1864,6 +1886,7 @@ li[data-name="following"] a { /* Legacy Badges */ +.ffz-legacy-mod-badges .ember-chat .badges .moderator, .ffz-legacy-badges .ember-chat .badges .moderator { background-color: #068c10; background-image: url('legacy-mod.png'); @@ -1884,6 +1907,7 @@ li[data-name="following"] a { background-image: url('legacy-admin.png'); } +.ffz-legacy-turbo-badges .ember-chat .badges .turbo, .ffz-legacy-badges .ember-chat .badges .turbo { background-color: #6441a3; background-image: url('legacy-turbo.png');