From 262757a20d429050e780fe6167b67d1c4b846d34 Mon Sep 17 00:00:00 2001
From: SirStendec
Date: Mon, 13 Nov 2017 01:23:39 -0500
Subject: [PATCH] 4.0.0 Beta 1
---
.gitignore | 1 +
README.md | 28 +-
changelog.html | 69 +-
dark.css | 2216 -----
gulpfile.js | 292 -
old_changes.html | 69 +
package-lock.json | 7387 +++++++++++++++++
package.json | 63 +-
res/font/ffz-fontello.eot | Bin 0 -> 12396 bytes
res/font/ffz-fontello.svg | 66 +
res/font/ffz-fontello.ttf | Bin 0 -> 12212 bytes
res/font/ffz-fontello.woff | Bin 0 -> 7408 bytes
res/font/ffz-fontello.woff2 | Bin 0 -> 6256 bytes
src/FileSaver.js | 188 -
src/ObjectPath.js | 21 -
src/badges.js | 847 --
src/commands.js | 531 --
src/constants.js | 146 -
src/debug.js | 38 -
src/ember/bits.js | 335 -
src/ember/channel.js | 754 --
src/ember/chat-input.js | 1569 ----
src/ember/chatview.js | 1501 ----
src/ember/commerce.js | 330 -
src/ember/conversations.js | 271 -
src/ember/dashboard.js | 62 -
src/ember/directory.js | 793 --
src/ember/feed-card.js | 121 -
src/ember/following.js | 336 -
src/ember/layout.js | 461 -
src/ember/line.js | 2127 -----
src/ember/moderation-card.js | 1825 ----
src/ember/player.js | 307 -
src/ember/room.js | 2887 -------
src/ember/router.js | 47 -
src/ember/sidebar.js | 514 --
src/ember/viewers.js | 105 -
src/ember/vod-chat.js | 164 -
src/ember/wrapper.js | 99 -
src/emoticons.js | 572 --
src/entry.js | 16 +
src/ext/api.js | 682 --
src/ext/betterttv.js | 539 --
src/ext/emote_menu.js | 65 -
src/ext/warpworld.js | 31 -
src/featurefriday.js | 149 -
src/i18n.js | 489 ++
src/jsconfig.json | 8 +
src/less/dark-clips.less | 417 -
src/less/style-clips.less | 5 -
src/localization.js | 6 -
src/main.js | 717 +-
src/modules/chat/badges.js | 27 +
src/modules/chat/emotes.js | 325 +
src/modules/chat/index.js | 513 ++
src/modules/chat/room.js | 216 +
src/modules/chat/tokenizers.js | 702 ++
src/modules/main_menu/components.js | 3 +
.../main_menu/components/changelog.vue | 50 +
.../main_menu/components/feedback-page.vue | 35 +
.../main_menu/components/filter-editor.vue | 67 +
.../main_menu/components/home-page.vue | 100 +
.../main_menu/components/main-menu.vue | 180 +
.../main_menu/components/menu-container.vue | 34 +
.../main_menu/components/menu-page.vue | 89 +
.../main_menu/components/menu-tree.vue | 165 +
.../main_menu/components/profile-editor.vue | 205 +
.../main_menu/components/profile-manager.vue | 129 +
.../main_menu/components/profile-selector.vue | 218 +
.../components/setting-check-box.vue | 58 +
.../main_menu/components/setting-hotkey.vue | 44 +
.../components/setting-radio-buttons.vue | 27 +
.../components/setting-select-box.vue | 64 +
.../main_menu/components/setting-text-box.vue | 59 +
src/modules/main_menu/index.js | 551 ++
src/modules/main_menu/setting-mixin.js | 178 +
src/modules/metadata.js | 342 +
src/modules/tooltips.js | 93 +
src/modules/translation_ui/components.js | 3 +
src/modules/translation_ui/nondex.js | 42 +
src/settings.js | 870 --
src/settings/context.js | 287 +
src/settings/index.js | 452 +
src/settings/migration.js | 16 +
src/settings/profile.js | 178 +
src/settings/providers.js | 301 +
src/shims.js | 24 -
src/sites/base.js | 106 +
src/sites/twitch-twilight/index.js | 113 +
.../twitch-twilight/modules/channel_bar.js | 93 +
.../twitch-twilight/modules/chat/index.js | 704 ++
.../modules/css_tweaks/index.js | 214 +
.../modules/css_tweaks/styles.js | 3 +
.../styles/chat-borders-3d-inset.scss | 19 +
.../css_tweaks/styles/chat-borders-3d.scss | 19 +
.../css_tweaks/styles/chat-borders-wide.scss | 17 +
.../css_tweaks/styles/chat-borders.scss | 17 +
.../css_tweaks/styles/chat-padding.scss | 7 +
.../modules/css_tweaks/styles/chat-rows.scss | 16 +
.../modules/css_tweaks/styles/hide-bits.scss | 4 +
.../css_tweaks/styles/minimal-navigation.scss | 12 +
.../css_tweaks/styles/swap-sidebars.scss | 33 +
.../twitch-twilight/modules/menu_button.js | 113 +
src/sites/twitch-twilight/modules/player.js | 101 +
.../twitch-twilight/modules/theme/index.js | 75 +
src/sites/twitch-twilight/styles/channel.scss | 21 +
src/sites/twitch-twilight/styles/chat.scss | 3 +
src/sites/twitch-twilight/styles/main.scss | 8 +
.../twitch-twilight/styles/main_menu.scss | 50 +
.../twitch-twilight/styles/menu_button.scss | 15 +
src/sites/twitch-twilight/styles/player.scss | 13 +
src/socket.js | 856 +-
src/std-components/index.js | 3 +
src/std-components/tab-container.vue | 118 +
src/styles/badges-blank.css | 3 -
src/styles/badges-circular-small.css | 5 -
src/styles/badges-circular.css | 6 -
src/styles/badges-legacy-mod.css | 9 -
src/styles/badges-legacy-turbo.css | 9 -
src/styles/badges-legacy.css | 29 -
src/styles/badges-rounded.css | 4 -
src/styles/badges-sub-notice-on.css | 1 -
src/styles/badges-sub-notice.css | 1 -
src/styles/badges-transparent.css | 21 -
src/styles/chat-background.css | 113 -
src/styles/chat-colors-gray.css | 4 -
src/styles/chat-hc-background.css | 20 -
src/styles/chat-hc-bold.css | 6 -
src/styles/chat-hc-text.css | 23 -
src/styles/chat-padding.css | 23 -
src/styles/chat-separator-3d-inset.css | 21 -
src/styles/chat-separator-3d.css | 19 -
src/styles/chat-separator-wide.css | 19 -
src/styles/chat-separator.css | 33 -
src/styles/chat-user-bg.css | 21 -
src/tokenize.js | 2003 -----
src/ui/about_page.js | 655 --
src/ui/channel_stats.js | 409 -
src/ui/dark.js | 266 -
src/ui/dash_feed.js | 197 -
src/ui/dash_stats.js | 307 -
src/ui/following-count.js | 350 -
src/ui/following.js | 335 -
src/ui/logviewer.js | 893 --
src/ui/menu.js | 850 --
src/ui/menu_button.js | 46 -
src/ui/my_emotes.js | 807 --
src/ui/notifications.js | 206 -
src/ui/popups.js | 90 -
src/ui/races.js | 255 -
src/ui/schedule.js | 218 -
src/ui/styles.js | 75 -
src/ui/sub_count.js | 87 -
src/ui/tooltips.js | 428 -
src/ui/viewer_count.js | 72 -
src/{colors.js => utilities/color.js} | 513 +-
src/utilities/compat/apollo.js | 308 +
src/utilities/compat/fine-router.js | 87 +
src/utilities/compat/fine.js | 486 ++
src/utilities/compat/webmunch.js | 168 +
src/utilities/constants.js | 22 +
src/utilities/dom.js | 149 +
src/utilities/events.js | 313 +
src/utilities/filtering.js | 6 +
src/utilities/logging.js | 73 +
src/utilities/module.js | 563 ++
src/utilities/object.js | 167 +
src/utilities/time.js | 24 +
src/utilities/tooltip.js | 296 +
src/utilities/vue.js | 99 +
src/utils.js | 1408 ----
style.css | 4748 -----------
styles/chat.scss | 58 +
styles/icons.scss | 91 +
styles/main.scss | 12 +
styles/theme.scss | 2520 ++++++
styles/tooltips.scss | 401 +
styles/widgets.scss | 162 +
styles/widgets/container.scss | 73 +
styles/widgets/menu-container.scss | 27 +
styles/widgets/menu-tree.scss | 69 +
styles/widgets/profile-selector.scss | 64 +
styles/widgets/tab-container.scss | 40 +
webpack.common.js | 66 +
webpack.web.common.js | 11 +
webpack.web.dev.js | 63 +
webpack.web.prod.js | 68 +
187 files changed, 22878 insertions(+), 38882 deletions(-)
delete mode 100644 dark.css
delete mode 100644 gulpfile.js
create mode 100644 package-lock.json
create mode 100644 res/font/ffz-fontello.eot
create mode 100644 res/font/ffz-fontello.svg
create mode 100644 res/font/ffz-fontello.ttf
create mode 100644 res/font/ffz-fontello.woff
create mode 100644 res/font/ffz-fontello.woff2
delete mode 100644 src/FileSaver.js
delete mode 100644 src/ObjectPath.js
delete mode 100644 src/badges.js
delete mode 100644 src/commands.js
delete mode 100644 src/constants.js
delete mode 100644 src/debug.js
delete mode 100644 src/ember/bits.js
delete mode 100644 src/ember/channel.js
delete mode 100644 src/ember/chat-input.js
delete mode 100644 src/ember/chatview.js
delete mode 100644 src/ember/commerce.js
delete mode 100644 src/ember/conversations.js
delete mode 100644 src/ember/dashboard.js
delete mode 100644 src/ember/directory.js
delete mode 100644 src/ember/feed-card.js
delete mode 100644 src/ember/following.js
delete mode 100644 src/ember/layout.js
delete mode 100644 src/ember/line.js
delete mode 100644 src/ember/moderation-card.js
delete mode 100644 src/ember/player.js
delete mode 100644 src/ember/room.js
delete mode 100644 src/ember/router.js
delete mode 100644 src/ember/sidebar.js
delete mode 100644 src/ember/viewers.js
delete mode 100644 src/ember/vod-chat.js
delete mode 100644 src/ember/wrapper.js
delete mode 100644 src/emoticons.js
create mode 100644 src/entry.js
delete mode 100644 src/ext/api.js
delete mode 100644 src/ext/betterttv.js
delete mode 100644 src/ext/emote_menu.js
delete mode 100644 src/ext/warpworld.js
delete mode 100644 src/featurefriday.js
create mode 100644 src/i18n.js
create mode 100644 src/jsconfig.json
delete mode 100644 src/less/dark-clips.less
delete mode 100644 src/less/style-clips.less
delete mode 100644 src/localization.js
create mode 100644 src/modules/chat/badges.js
create mode 100644 src/modules/chat/emotes.js
create mode 100644 src/modules/chat/index.js
create mode 100644 src/modules/chat/room.js
create mode 100644 src/modules/chat/tokenizers.js
create mode 100644 src/modules/main_menu/components.js
create mode 100644 src/modules/main_menu/components/changelog.vue
create mode 100644 src/modules/main_menu/components/feedback-page.vue
create mode 100644 src/modules/main_menu/components/filter-editor.vue
create mode 100644 src/modules/main_menu/components/home-page.vue
create mode 100644 src/modules/main_menu/components/main-menu.vue
create mode 100644 src/modules/main_menu/components/menu-container.vue
create mode 100644 src/modules/main_menu/components/menu-page.vue
create mode 100644 src/modules/main_menu/components/menu-tree.vue
create mode 100644 src/modules/main_menu/components/profile-editor.vue
create mode 100644 src/modules/main_menu/components/profile-manager.vue
create mode 100644 src/modules/main_menu/components/profile-selector.vue
create mode 100644 src/modules/main_menu/components/setting-check-box.vue
create mode 100644 src/modules/main_menu/components/setting-hotkey.vue
create mode 100644 src/modules/main_menu/components/setting-radio-buttons.vue
create mode 100644 src/modules/main_menu/components/setting-select-box.vue
create mode 100644 src/modules/main_menu/components/setting-text-box.vue
create mode 100644 src/modules/main_menu/index.js
create mode 100644 src/modules/main_menu/setting-mixin.js
create mode 100644 src/modules/metadata.js
create mode 100644 src/modules/tooltips.js
create mode 100644 src/modules/translation_ui/components.js
create mode 100644 src/modules/translation_ui/nondex.js
delete mode 100644 src/settings.js
create mode 100644 src/settings/context.js
create mode 100644 src/settings/index.js
create mode 100644 src/settings/migration.js
create mode 100644 src/settings/profile.js
create mode 100644 src/settings/providers.js
delete mode 100644 src/shims.js
create mode 100644 src/sites/base.js
create mode 100644 src/sites/twitch-twilight/index.js
create mode 100644 src/sites/twitch-twilight/modules/channel_bar.js
create mode 100644 src/sites/twitch-twilight/modules/chat/index.js
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/index.js
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles.js
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d-inset.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-wide.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-padding.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/hide-bits.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss
create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/swap-sidebars.scss
create mode 100644 src/sites/twitch-twilight/modules/menu_button.js
create mode 100644 src/sites/twitch-twilight/modules/player.js
create mode 100644 src/sites/twitch-twilight/modules/theme/index.js
create mode 100644 src/sites/twitch-twilight/styles/channel.scss
create mode 100644 src/sites/twitch-twilight/styles/chat.scss
create mode 100644 src/sites/twitch-twilight/styles/main.scss
create mode 100644 src/sites/twitch-twilight/styles/main_menu.scss
create mode 100644 src/sites/twitch-twilight/styles/menu_button.scss
create mode 100644 src/sites/twitch-twilight/styles/player.scss
create mode 100644 src/std-components/index.js
create mode 100644 src/std-components/tab-container.vue
delete mode 100644 src/styles/badges-blank.css
delete mode 100644 src/styles/badges-circular-small.css
delete mode 100644 src/styles/badges-circular.css
delete mode 100644 src/styles/badges-legacy-mod.css
delete mode 100644 src/styles/badges-legacy-turbo.css
delete mode 100644 src/styles/badges-legacy.css
delete mode 100644 src/styles/badges-rounded.css
delete mode 100644 src/styles/badges-sub-notice-on.css
delete mode 100644 src/styles/badges-sub-notice.css
delete mode 100644 src/styles/badges-transparent.css
delete mode 100644 src/styles/chat-background.css
delete mode 100644 src/styles/chat-colors-gray.css
delete mode 100644 src/styles/chat-hc-background.css
delete mode 100644 src/styles/chat-hc-bold.css
delete mode 100644 src/styles/chat-hc-text.css
delete mode 100644 src/styles/chat-padding.css
delete mode 100644 src/styles/chat-separator-3d-inset.css
delete mode 100644 src/styles/chat-separator-3d.css
delete mode 100644 src/styles/chat-separator-wide.css
delete mode 100644 src/styles/chat-separator.css
delete mode 100644 src/styles/chat-user-bg.css
delete mode 100644 src/tokenize.js
delete mode 100644 src/ui/about_page.js
delete mode 100644 src/ui/channel_stats.js
delete mode 100644 src/ui/dark.js
delete mode 100644 src/ui/dash_feed.js
delete mode 100644 src/ui/dash_stats.js
delete mode 100644 src/ui/following-count.js
delete mode 100644 src/ui/following.js
delete mode 100644 src/ui/logviewer.js
delete mode 100644 src/ui/menu.js
delete mode 100644 src/ui/menu_button.js
delete mode 100644 src/ui/my_emotes.js
delete mode 100644 src/ui/notifications.js
delete mode 100644 src/ui/popups.js
delete mode 100644 src/ui/races.js
delete mode 100644 src/ui/schedule.js
delete mode 100644 src/ui/styles.js
delete mode 100644 src/ui/sub_count.js
delete mode 100644 src/ui/tooltips.js
delete mode 100644 src/ui/viewer_count.js
rename src/{colors.js => utilities/color.js} (54%)
create mode 100644 src/utilities/compat/apollo.js
create mode 100644 src/utilities/compat/fine-router.js
create mode 100644 src/utilities/compat/fine.js
create mode 100644 src/utilities/compat/webmunch.js
create mode 100644 src/utilities/constants.js
create mode 100644 src/utilities/dom.js
create mode 100644 src/utilities/events.js
create mode 100644 src/utilities/filtering.js
create mode 100644 src/utilities/logging.js
create mode 100644 src/utilities/module.js
create mode 100644 src/utilities/object.js
create mode 100644 src/utilities/time.js
create mode 100644 src/utilities/tooltip.js
create mode 100644 src/utilities/vue.js
delete mode 100644 src/utils.js
delete mode 100644 style.css
create mode 100644 styles/chat.scss
create mode 100644 styles/icons.scss
create mode 100644 styles/main.scss
create mode 100644 styles/theme.scss
create mode 100644 styles/tooltips.scss
create mode 100644 styles/widgets.scss
create mode 100644 styles/widgets/container.scss
create mode 100644 styles/widgets/menu-container.scss
create mode 100644 styles/widgets/menu-tree.scss
create mode 100644 styles/widgets/profile-selector.scss
create mode 100644 styles/widgets/tab-container.scss
create mode 100644 webpack.common.js
create mode 100644 webpack.web.common.js
create mode 100644 webpack.web.dev.js
create mode 100644 webpack.web.prod.js
diff --git a/.gitignore b/.gitignore
index 2d22fb2f..fb3e9c14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
node_modules
npm-debug.log
build
+dist
Extension Building
Old Files
badges
diff --git a/README.md b/README.md
index 4b8f5dd0..cbf1a4ce 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
FrankerFaceZ
============
-Copyright (c) 2016 Dan Salvato LLC
+Copyright (c) 2017 Dan Salvato LLC
Licensed under the Apache License, Version 2.0. See LICENSE.
@@ -12,26 +12,18 @@ Developing
FrankerFaceZ uses node.js to manage development dependencies and to run an HTTP
server for development. To get everything you need:
-1. Install node.js
-2. Run ```npm install -g gulp``` to install the ```gulp``` command line utility.
-3. Run ```npm install``` within the FrankerFaceZ directory.
+1. Install node.js and npm
+2. Run ```npm install`` within the FrankerFaceZ directory.
-From there, you can use gulp to build the extension from source simply by
-running ```gulp```. For development, you can instruct gulp to watch the source
-files for changes and re-build automatically with ```gulp watch```
+From there, you can use npm to build the extension from source simply by
+running ```npm run build```. For development, you can instruct gulp to watch
+the source files for changes and re-build automatically with ```npm start```
FrankerFaceZ comes with a local development server that listens on port 8000
and it serves up local development copies of files, falling back to the CDN
-when a local copy of a file isn't present. To start the server,
-run ```gulp server```
+when a local copy of a file isn't present.
-For convenience, the server is run automatically along with ```gulp watch```
-
-
-Use the command ```/ffz developer_mode on``` or ```/ffz developer_mode off```
-in Twitch chat to toggle developer mode on or off. You must then refresh the
-page for changes to take effect. If FFZ is not working or the command otherwise
-fails to work, you can open the JavaScript console on twitch.tv and run
-```localStorage.ffzDebugMode = true;``` or
-```localStorage.ffzDebugMode = false;``` to enable or disable the feature.
\ No newline at end of file
+To make FrankerFaceZ load from your local development server, you must set
+the local storage variable ```ffzDebugMode``` to true. Just run the following
+in your console on Twitch: ```localStorage.ffzDebugMode = true;```
diff --git a/changelog.html b/changelog.html
index b38085b3..96dad766 100644
--- a/changelog.html
+++ b/changelog.html
@@ -1,70 +1,7 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dark.css b/dark.css
deleted file mode 100644
index e4689ba3..00000000
--- a/dark.css
+++ /dev/null
@@ -1,2216 +0,0 @@
-/* host mode */
-.ffz-dark #hostmode,.hostmode{
- background-color:rgb(16,16,16)!important;
-}
-
-.ffz-dark div#channel > .target-frame {
- background-color:rgb(16,16,16)!important;
-}
-
-.ffz-dark .hostmode-title-container{
- background-color:rgb(16,16,16)!important;
- box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset;
-}
-
-.ffz-dark div.hostmode-title.clearfix{
- background-color:rgb(24,24,24)!important;
- box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset;
-}
-
-.ffz-dark .close-hostmode{
- background-color:rgb(16,16,16)!important;
- border-top: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.ffz-dark .offlineChannelStatus {
- background-color: rgba(255,255,255, 0.05);
-}
-
-.ffz-dark .close-hostmode a:before,
-.ffz-dark .close-hostmode a:after {
- border-bottom-color: rgba(255,255,255, 0.05);
-}
-
-
-/* hidden chat */
-.ffz-dark .card__img,
-.ffz-dark .ember-chat .chat-hidden-overlay{
- background-color:rgb(24,24,24)!important;
-}
-
-
-
-/* edit icon */
-.ffz-dark .js-video-stats__video-header + a svg,
-.ffz-dark .subscription-modal__balloon svg,
-.ffz-dark #channel .player-column #broadcast-meta .info .edit-link svg path{
- fill:rgba(255,255,255,0.5)!important;
-}
-
-/* vod icons */
-.ffz-dark #stats .stat svg:not(.svg-glyph_live) path,
-.ffz-dark #main_col .content #stats_and_actions #channel_stats .stat svg path{
- fill:rgba(255,255,255,0.5)!important;
-}
-
-
-/* hover icon for chats menu */
-.ffz-dark .ember-chat .chat-menu-button-container:hover svg path{
- fill:#fff!important;
-}
-
-
-/* dropdown arrows */
-.ffz-dark .button.drop:after{
- border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;;
-}
-
-/* hovering buttons */
-.ffz-dark .button:not(.primary):not(.ffz-donate):hover{
- color:rgb(222,222,222)!important;
-}
-
-.ffz-dark .moderation-card .button:not(.button--icon-only):hover {
- color: #fff !important;
-}
-
-/* stats */
-.ffz-dark .ct-tags--extracted { border-bottom: none }
-
-.ffz-dark .stats-and-actions,
-.ffz-dark #main_col .content #stats_and_actions {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #channel .player-column .stats-and-actions .channel-stats .stat svg:not(.svg-glyph_live) path{
- fill:rgba(255,255,255,0.35)!important;
-}
-
-/* Scrollbar */
-.ffz-dark .tse-scrollbar .drag-handle {
- background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.6)!important;
-}
-
-
-/* Team Pages */
-
-.ffz-dark #stats_and_description { border-color: rgba(255,255,255,0.1) }
-.ffz-dark #stats_and_description .stat { color: #808080 }
-.ffz-dark #stats_and_description .stat:not(#channel_viewer_count) { -webkit-filter: invert(100%) }
-
-.ffz-dark .profile-card__content,
-.ffz-dark .member.js-playing a { color: #fff !important }
-.ffz-dark .member { box-shadow: none !important }
-.ffz-dark .member.live { background-color: #222; }
-.ffz-dark .member:hover { background-color: #333 }
-.ffz-dark .member .channel_count { color: #ccc }
-
-
-/* main column */
-.ffz-dark .fp-container,
-body.ffz-dark:not([data-page="teams#show"]),
-.ffz-dark .app-main,
-.ffz-dark[data-page="teams#show"] .main,
-.ffz-dark:not([data-page="teams#show"]) div#mantle_skin,
-.ffz-dark div#main_col {
- background:rgb(16,16,16);
- color:rgb(195,195,195)!important;
- border-right: 0 !important; /*1px solid rgb(0,0,0)!important;*/
-}
-
-.ffz-dark.ffz-portrait div#left_col .column,
-.ffz-dark.ffz-portrait div#main_col {
- border-right: none !important;
- border-bottom: 1px solid rgb(0,0,0) !important;
-}
-
-
-/* stream title */
-.ffz-dark span.real_title{
- color:rgb(245,245,245)!important;
-}
-
-/* name playing x on x */
-.ffz-dark span.playing,span#team_membership,.ffz-dark #channel .player-column #broadcast-meta .info .channel {
- color:rgb(195,195,195)!important;
-}
-
-.ffz-dark div.title > span.real,
-.ffz-dark div.title > span.over,
-.ffz-dark #broadcast-meta .info .title {
- color: #DEDEDE !important;
- background-color: rgba(16,16,16,0.3) !important;
-}
-
-.ffz-dark .player-placeholder { background: #000 }
-
-.ffz-dark div.title > span.over:hover,
-.ffz-dark div.title > span.real:hover,
-.ffz-dark #broadcast-meta .info .title:hover {
- color: #fff !important;
- background-color: #101010 !important;
-}
-
-
-/* Right Column */
-
-.ffz-dark .ct-banner--off .ct-banner__toggle,
-.ffz-dark #right_col {
- background-color: rgb(25,25,31);
- color: #fff;
-}
-
-.ffz-dark.ffz-no-blue #right_col,
-.ffz-dark.ffz-no-blue .no-login-contain li,
-.ffz-dark.ffz-no-blue .following-col .following-list .load-more span,
-.ffz-dark.ffz-no-blue .viewall a,
-.ffz-dark.ffz-no-blue #new-user-prompt {
- background-color: rgb(25,25,25);
-}
-
-
-/* Popups */
-
-.ffz-dark #commission_modal {
- background-color: #101010 !important;
- border-color: #32323e !important;
-}
-
-.ffz-dark .twitch_subwindow_container.two-factor-auth .card,
-.ffz-dark .card#passport_modal {
- color: #000;
-}
-
-.ffz-dark .subdash-feature-notice,
-.ffz-dark #channel-prompt,
-.ffz-dark .balloon,
-.ffz-dark .balloon:after,
-.ffz-dark .conversation-settings-menu,
-.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.ffz-dark .twitch_subwindow_container:not(.two-factor-auth) .card:not(#passport_modal),
-.ffz-dark #flyout .content,
-.ffz-dark .whatisthis,
-.ffz-dark .ui-menu,
-.ffz-dark .dropmenu,
-.ffz-dark .sort-contain .sort-options,
-.ffz-dark .top-dropdown,
-.ffz-dark form.js-new_panel_form,
-.ffz-dark .js-new_panel_btn,
-.ffz-dark .manager .videos-grid .video .meta,
-.ffz-dark .ember-chat .chat-room-list,
-.ffz-dark .st-autocomplete-sidebar,
-.ffz-dark .st-autocomplete-small,
-.ffz-dark .st-autocomplete,
-.ffz-dark .st-autocomplete-small,
-.ffz-dark .player-menu__menu {
- background-color: rgb(16,16,16);
- color: rgb(195,195,195); /*#acacbf;*/
- border-color: #32323e;
- box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset;
-}
-
-.ffz-dark .balloon--tooltip:after,
-.ffz-dark .balloon--tooltip {
- background-color: #000;
-}
-
-.ffz-dark .chat-container { border-color: rgba(0,0,0,0.2) }
-
-.ffz-dark #flyout .content,
-.ffz-dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box { border: none }
-
-.ffz-dark .balloon--right.balloon--up:after,
-.ffz-dark .balloon--up:after { box-shadow: 1px 1px 0 rgba(255,255,255,0.2) }
-.ffz-dark .balloon--right.balloon--down:after,
-.ffz-dark .balloon--down:after { box-shadow: -1px -1px 0 rgba(255,255,255,0.2) }
-.ffz-dark .balloon--left:after { box-shadow: 1px -1px 0 rgba(255,255,255,0.2) }
-.ffz-dark .balloon--right:after { box-shadow: -1px 1px 0 rgba(255,255,255,0.2) }
-
-.ffz-dark .filter-bar__balloon-footer,
-.ffz-dark .balloon__footer {
- background-color: rgb(25,25,25);
- border: 1px solid rgba(255,255,255,0.2);
-}
-
-
-.ffz-dark .player-menu__header { color: #c3c3c3 }
-.ffz-dark .player-menu__section { border-bottom-color: rgba(255,255,255,0.2) }
-
-/*.ffz-dark .balloon:after { box-shadow: none }*/
-
-.ffz-dark .st-autocomplete-sidebar .label,
-.ffz-dark .st-autocomplete-small .label,
-.ffz-dark .st-autocomplete .label,
-.ffz-dark .st-autocomplete-small .label {
- background-color: rgb(24,24,24);
-}
-
-.ffz-dark .js-new_panel_btn:hover {
- background-color: rgb(24,24,24);
-}
-
-.ffz-dark .player-menu__menu:before,
-.ffz-dark .player-menu__menu:after {
- border-top-color: rgb(16,16,16);
-}
-
-.ffz-dark .st-autocomplete-sidebar:before,
-.ffz-dark #flyout .point:before {
- border-right-color: rgb(16,16,16); /*rgb(25,25,31);*/
-}
-
-.ffz-dark .st-autocomplete-sidebar:after,
-.ffz-dark #flyout .point:after {
- border-right-color: #32323e;
-}
-
-.ffz-dark .notification-controls .toggle-notification-menu {
- background-color: #0f8a4d;
-}
-
-.ffz-dark .notification-controls .toggle-notification-menu:hover {
- background-color: #119754;
-}
-
-.ffz-dark .pika-input,
-.ffz-dark .video-stats .form__input[type=search],
-.ffz-dark .change-banner .banner-preview,
-.ffz-dark .ct-banner--off .ct-banner__inputbox,
-.ffz-dark form.js-new_panel_form input,
-.ffz-dark form.js-new_panel_form textarea,
-.ffz-dark .conversation-input-bar textarea,
-.ffz-dark .card input,
-.ffz-dark .card textarea,
-.ffz-dark .dropmenu input,
-.ffz-dark input.text,
-.ffz-dark input.string,
-.ffz-dark #sidebar_query_small,
-.ffz-dark textarea,
-.ffz-dark select,
-.ffz-dark option,
-.ffz-dark #mantle_skin .dropdown,
-.ffz-dark .directory_header #custom_filter input {
- background-color: rgba(255,255,255,0.05);
- border-color: rgba(255,255,255,0.1);
- color: #fff;
-}
-
-.ffz-dark option {
- background-color: #191919;
-}
-
-.ffz-dark select.pl-form__input,
-.ffz-dark .video-stats .form__input[type=search]:not(:focus):not(:hover) {
- box-shadow: inset 0 0 1px rgba(255,255,255,0.2);
-}
-
-.ffz-dark .video-stats .table__cell--header,
-.ffz-dark .video-stats__table-title {
- color: #ccc;
- box-shadow: inset 0 -1px 0 rgba(255,255,255,0.2);
-}
-
-.ffz-dark .video-stats .table__header {
- background-color: #191919;
- color: #ccc;
-}
-
-.ffz-dark .video-stats .table {
- border-color: rgba(255,255,255,0.2);
- background-color: #101010;
- box-shadow: 0 2px 4px 0 rgba(255,255,255,0.2);
-}
-
-.ffz-dark .video-stats .table__row {
- background-color: #101010;
- color: #999;
-}
-
-.ffz-dark .video-stats .table__cell {
- border-color: rgba(255,255,255,0.2);
- color: #999;
-}
-
-
-/* Other stuff */
-
-.ffz-dark #channel .player-column #broadcast-meta .info .edit-link span {
- color: inherit;
-}
-
-.ffz-dark .no-login-contain li {
- background-color: rgb(25,25,31);
-}
-
-.ffz-dark .no-login-contain h3 {
- color: #ccc;
-}
-
-.ffz-dark .panel-formatting .panel {
- color: #8c8c9c;
-}
-
-.ffz-dark .balloon .balloon__link { color: #a68ed2 !important }
-.ffz-dark .balloon .balloon__link--active,
-.ffz-dark .balloon .balloon__link--selected,
-.ffz-dark .balloon .balloon__link:hover { color: #fff !important }
-.ffz-dark .balloon .balloon__link--selected { background-color: #6441a5 !important }
-
-.ffz-dark .st-autocomplete-sidebar .all p:not(.active),
-.ffz-dark .st-autocomplete-small .all p:not(.active),
-.ffz-dark .st-autocomplete .all p:not(.active),
-.ffz-dark .st-autocomplete-small .all p:not(.active),
-.ffz-dark .st-autocomplete-sidebar .list ul .result:not(.active) p,
-.ffz-dark .st-autocomplete-small .list ul .result:not(.active) p,
-.ffz-dark .st-autocomplete .list ul .result:not(.active) p,
-.ffz-dark .st-autocomplete-small .list ul .result:not(.active) p,
-.ffz-dark .manager .videos-grid .video:hover .meta .actions li a,
-.ffz-dark .ember-chat .chat-room-list .room:not(:hover) p.room-title,
-.ffz-dark .dropmenu_action:not(:hover),
-.ffz-dark .dropmenu_action:not(:hover) span,
-.ffz-dark .chat-menu-content button.font-color-purple,
-.ffz-dark .player a.player-text-link,
-.ffz-dark .cn-metabar .cn-metabar__title a,
-.ffz-dark a {
-/*.ffz-dark a:not(.profile-card__content):not(.filter-item):not(.ui-state-focus):not(.button):not(.switch):not(.follow):not(.fb_button):not(.what) {*/
- color: #a68ed2;
-}
-
-.ffz-dark .balloon--cols .balloon__list~.balloon__list {
- box-shadow: -1px 0 0 rgba(255,255,255,0.2);
-}
-
-.ffz-dark .top-nav__nav-link:hover { color: #fff !important }
-
-.ffz-dark .warp__item a.js-language-select,
-.ffz-dark .top-nav__nav-link,
-.ffz-dark .warp__item > a { color: #d5d4d9 !important }
-
-.ffz-dark .warp__item--toggled a.js-language-select,
-.ffz-dark .warp__item > a:hover,
-.ffz-dark .warp__item--toggled > a { color: #eae9ec !important }
-
-.ffz-dark .exit-theatre > a { color: #000 !important; }
-
-.ffz-dark .cheermote-tier {
- background-color: #111;
- border-color: #474747;
-}
-
-.ffz-dark .progress-bar {
- background-color: #444;
-}
-
-.ffz-dark .c-background-graph { background-color: #444 !important }
-
-.ffz-dark .shadow-border--bottom-right {
- box-shadow: 1px 0 0 0 #474747, 0 1px 0 0 #474747;
-}
-
-.ffz-dark .switch,
-.ffz-dark .button,
-.ffz-dark .pl-button--hollow,
-.ffz-dark .pl-button--hollow:hover,
-.ffz-dark .player-switch,
-.ffz-dark .follow-button a,
-.ffz-dark a.dropmenu_action:hover {
- color: #fff;
-}
-
-.ffz-dark .button:disabled {
- background-color: #242424;
-}
-
-.ffz-dark .pl-button--hollow,
-.ffz-dark .button--hollow { box-shadow: inset 0 0 0 1px #4b367c }
-
-.ffz-dark .panel-formatting .panel h3 {
- color: inherit;
-}
-
-.ffz-dark .follow-button .notify:before,
-.ffz-dark .button.drop:after,
-.ffz-dark .follow-button .drop.follow:after {
- border: 5px solid rgba(255,255,255,0.35);
- border-left-color: transparent;
- border-right-color: transparent;
- border-bottom-color: transparent;
-}
-
-.ffz-dark .follow-button .notify {
- background-color: #25252a;
-}
-
-.ffz-dark .message-button,
-.ffz-dark .button--text,
-.ffz-dark .button.ffz-no-bg {
- color: #a68ed2;
-}
-
-.ffz-dark .form__icon svg { fill: rgba(255,255,255,0.2) }
-
-.ffz-dark .button--icon.button--hollow figure svg,
-.ffz-dark .button.button--icon-only svg {
- fill: #a68ed2;
-}
-
-.ffz-dark .button.button--icon-only:hover svg {
- fill: #fff;
-}
-
-.ffz-dark .button.primary.subscribe-button {
- color: #fff;
-}
-
-.ffz-dark .ember-chat .moderation-card img.channel_logo {
- border-color: rgb(16,16,16);
-}
-
-/* Upsell banner */
-.ffz-dark .bookmark-meta,
-.ffz-dark .upsell-banner {
- background-color: rgba(25,25,31, 0.7);
-}
-
-.ffz-dark.ffz-no-blue .bookmark-meta,
-.ffz-dark.ffz-no-blue .upsell-banner {
- background-color: rgba(25,25,25, 0.7);
-}
-
-.ffz-dark .bookmark-meta .bookmark-title,
-.ffz-dark .upsell-banner .message .title {
- color: #ccc;
-}
-
-.ffz-dark .bookmark-meta {
- box-shadow: none;
-}
-
-/* VoD Description */
-
-.ffz-dark .archives-contain .videos {
- padding: 0 0;
-}
-
-.ffz-dark #right_col a.bottom,
-.ffz-dark .archives-contain .list-video {
- padding: 10px 20px;
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #right_col a.bottom {
- padding: 0 20px;
- margin: 0 !important;
-}
-
-.ffz-dark .archives-contain .list-video .meta .title {
- color: rgb(195,195,195);
-}
-
-.ffz-dark #right_col #archives { background-color: transparent; }
-
-.ffz-dark .archives-contain .list-video .meta .info p.broadcaster,
-.ffz-dark #right_col #archives .more_archives {
- color: #a68ed2;
- box-shadow: none;
-}
-
-.ffz-dark #right_col a.bottom:hover,
-.ffz-dark .archives-contain .list-video:hover,
-.ffz-dark #right_col #archives .more_archives:hover {
- background-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark #main_col .content .archive_info p,
-.ffz-dark #main_col .content .archive_info time {
- color: #888;
-}
-
-
-/* Video Manager */
-
-.ffz-dark .player-menu-options,
-.ffz-dark ul.tabs:before,
-.ffz-dark .directory_header .nav:before,
-.ffz-dark ul.tabs_fake:before,
-.ffz-dark #right_col #archives .more_archives,
-.ffz-dark .manager .videos-grid .video .meta .actions {
- border-top-color: rgba(255,255,255,0.25);
-}
-
-.ffz-dark .twitch_subwindow_container .card .text-content .content-header,
-.ffz-dark .kraken-embed .card .text-content .content-header,
-.ffz-dark .kraken-page .card .text-content .content-header,
-.ffz-dark .twitch_subwindow_container .card .buttons,
-.ffz-dark .kraken-embed .card .buttons,
-.ffz-dark .kraken-page .card .buttons,
-.ffz-dark .card--bordered,
-.ffz-dark .card-vod-edit {
- border-color: #474747;
-}
-
-.ffz-dark .playlist-menu-create {
- background-color: #101010;
- border-color: #474747;
-}
-
-.ffz-dark .button--disabled {
- background-color: #333;
-}
-
-.ffz-dark .card-vod-edit {
- box-shadow: 0 2px 4px 0 rgba(255,255,255,0.1);
-}
-
-.ffz-dark #highlighter .highlight-content .form-container li label {
- color: #fff;
-}
-
-.ffz-dark .tabs.tabs--fullwidth li:not(.selected) a:hover {
- color: #fff !important;
- background: #24242a;
- border-color: #8c8c9c;
-}
-
-.ffz-dark .tabs li.selected a {
- color: #fff !important;
- border-color: #a68cd4 !important;
-}
-
-
-/* Subscriptions Page */
-
-.ffz-dark .ticket__benefits,
-.ffz-dark .tickets .ticket .benefits {
- background-color: rgb(16,16,16);
- color: rgb(195,195,195); /*#acacbf;*/
- border-color: #32323e;
-}
-
-
-/* Directory Pages */
-
-.ffz-dark .following-col .col-header .search-contain .search .search-button svg path {
- fill: rgba(255,255,255,0.25);
-}
-
-.ffz-dark .following-col .col-header .search-contain .search .search-button:hover svg path {
- fill: rgba(255,255,255,0.5);
-}
-
-.ffz-dark .following-col .following-list .load-more span,
-.ffz-dark .viewall a {
- background-color: rgb(25,25,31);
-}
-
-.ffz-dark .following-col .following-list .load-more:hover span {
- color: #fff;
-}
-
-.ffz-dark .following-col .following-list .load-more:hover span,
-.ffz-dark .viewall a:hover {
- background-color: rgb(35,35,41);
-}
-
-.ffz-dark.ffz-no-blue .following-col .following-list .load-more:hover span,
-.ffz-dark.ffz-no-blue .viewall a:hover {
- background-color: rgb(35,35,35);
-}
-
-.ffz-dark .viewall a .text span:first-child {
- color: #a68ed2;
-}
-
-
-/* Profile page fixes */
-
-.ffz-dark pre {
- background-color: rgb(8,8,8);
- box-shadow: inset 0 0 0 1px #333;
- border-left-color: #6441a5;
-}
-
-.ffz-dark .form__label,
-.ffz-dark .form__input[type=checkbox] + label,
-.ffz-dark .form__input[type=radio] + label,
-.ffz-dark label,
-.ffz-dark code {
- color: #999;
-}
-
-.ffz-dark .card__boxpin,
-.ffz-dark .streams .stream .content .thumb .boxart,
-.ffz-dark .videos .video .content .thumb .boxart {
- border-color: rgb(16,16,16);
-}
-
-.ffz-dark .ember-chat .moderation-card img.channel_logo,
-.ffz-dark .channel-link .profile-photo {
- background-color: rgb(16,16,16);
-}
-
-.ffz-dark .event-img--card { box-shadow: 1px 0 0 #101010 }
-
-.ffz-dark .toggle__button { background-color: #666 }
-.ffz-dark .toggle__button:after { background-color: #242424 }
-.ffz-dark .toggle input[type=checkbox]:checked+.toggle__button { background-color: #0f8a4d }
-
-
-.ffz-dark .card__title,
-.ffz-dark .card .card__title a,
-.ffz-dark .card .card__info a,
-.ffz-dark .items-grid .meta .title,
-.ffz-dark .items-grid .meta p a {
- color: #9c9c9c;
-}
-
-.ffz-dark .event-calendar {
- background: #101010;
- color: #ccc;
-}
-
-.ffz-dark .subscription-dash .video-quality-list li.non-selectables .option,
-.ffz-dark .subscription-dash .video-quality-list li {
- background-color: #191919;
- border-color: #474747;
-}
-
-.ffz-dark .subscription-dash .video-quality-list li .quality-setting {
- background-color: #000;
-}
-
-.ffz-dark .subscription-dash .video-quality-list li.selectable.over {
- background-image: none;
- background-color: #242424;
-}
-
-.ffz-dark .subscription-dash .video-quality-list li.selectable.restricted {
- background-color: #cc2b2b;
- border-color: #801b1b;
-}
-
-.ffz-dark .subscription-dash .video-quality-list li.selectable.restricted.over {
- background-color: #fc3636;
-}
-
-.ffz-dark .card__info,
-.ffz-dark .items-grid .meta .info {
- color: #6c6c6c;
-}
-
-.ffz-dark .mininav li > a,
-.ffz-dark ul.tabs li > a,
-.ffz-dark .directory_header .nav li > a,
-.ffz-dark .tw-tabs__item > button,
-.ffz-dark ul.tabs_fake li > a {
- color: #a68ed2;
-}
-
-.ffz-dark .tw-tabs,
-.ffz-dark .tabs { box-shadow: 0 -1px 0 rgba(255,255,255,0.2) inset; }
-
-.ffz-dark .tw-tabs__item > a:hover,
-.ffz-dark .tabs>.tab:hover,
-.ffz-dark .tabs>li:hover,
-.ffz-dark .tabs__item:hover,
-.ffz-dark .tabs__item--active,
-.ffz-dark .tabs__item.active,
-.ffz-dark .tabs__item.selected,
-.ffz-dark .tabs>li.selected,
-.ffz-dark .tabs>li>a.active { box-shadow: 0 -1px 0 #a68cd4 inset }
-
-.ffz-dark .mininav,
-.ffz-dark ul.tabs:before,
-.ffz-dark .direcotry_header .nav:before,
-.ffz-dark ul.tabs_fake:before {
- border-color: #32323e;
-}
-
-.ffz-dark .tw-tabs__item > button:hover,
-.ffz-dark .tw-tabs__item > a.active,
-.ffz-dark .tw-tabs__item > button.active {
- color: #ccc;
- box-shadow: 0 -1px 0 #ccc inset;
-}
-
-.ffz-dark .contract-amendments-window {
- border-color: #474747;
- background-color: #000;
-}
-
-.ffz-dark .game-details__nav > li > a.active { color: #fff }
-
-.ffz-dark .mininav li > a:hover,
-.ffz-dark ul.mininav li.active,
-.ffz-dark ul.tabs li.selected a,
-.ffz-dark .directory_header .nav li.selected a,
-.ffz-dark ul.tabs_fake li.selected a,
-.ffz-dark ul.tabs li>a:hover,
-.ffz-dark .directory_header .nav li>a:hover,
-.ffz-dark ul.tabs_fake li>a:hover,
-.ffz-dark ul.tabs li>a.active,
-.ffz-dark .directory_header .nav li>a.active,
-.ffz-dark ul.tabs_fake li>a.active {
- color: #aaa;
- border-color: #ccc !important;
-}
-
-/* Hide the chat Dark Mode control */
-.ffz-dark #theme-toggle,
-.ffz-dark .toggle-darkmode { display: none; }
-
-/* Chat Text Contrast */
-
-.ffz-dark .ember-chat .chat-settings .chat-colors .chat-colors-swatch:hover,
-.ffz-dark .ember-chat .chat-settings .chat-colors .chat-colors-switch.selected {
- border-color: #777;
- box-shadow: inset 0 0 0 1px #32323e;
-}
-
-
-/* Autocomplete Suggestions */
-
-.ffz-dark .ember-chat .chat-interface .suggestions {
- background-color: rgb(16,16,16);
- border-color: rgba(255,255,255,0.2);
-}
-
-
-/* FrankerFaceZ Menu */
-
-/*.ffz-dark .ember-chat .chat-menu .list-header {
- border-top-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-.ffz-dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading,
-.ffz-dark .ffz-ui-popup ul.menu,
-.ffz-dark .ffz-ui-popup ul.menu a,
-.ffz-sidebar-swap.ffz-dark .ffz-ui-popup ul.menu a {
- border-color: #32323e;
-}
-
-.ffz-dark .ffz-ui-popup ul.menu {
- background-color: rgb(33,33,33);
-}
-
-.ffz-dark .ffz-ui-popup ul.menu li.active {
- background-color: rgb(16,16,16);
-}
-
-.ffz-dark .ffz-ui-popup ul.menu li.active a {
- border-top-color: rgb(16,16,16);
-}*/
-
-/* New User Prompt */
-
-.ffz-dark #new-user-prompt {
- background-color: rgb(25,25,31);
- color: #acacbf;
-}
-
-.ffz-dark #new-user-prompt .message .title {
- color: #fff;
-}
-
-/* Messages Table */
-
-.ffz-dark .messages #send_message_form #bottom_buttons,
-.messages #reply_message_form #bottom_buttons {
- border-color: rgba(255,255,255,0.2);
- box-shadow: none;
-}
-
-.ffz-dark .messages #send_message_form .button_group,
-.ffz-dark .messages #reply_message_form .button_group,
-.ffz-dark .messages #send_message_form,
-.ffz-dark .messages #reply_message_form {
- background: transparent;
-}
-
-.ffz-dark .messages #send_message_form,
-.ffz-dark .messages #reply_message_form
-{ padding: 10px 0 0 0; }
-
-.ffz-dark .messages #message_actions {
- background-color: rgb(48,48,48);
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .messages #send_message_form,
-.ffz-dark .messages .divider {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .messages .message_action.delete {
- display: block;
- width: 16px;
- height: 16px;
- background: url(trash_button.png) no-repeat;
-}
-
-.ffz-dark .messages .message_action.delete img { display: none; }
-
-.ffz-dark .messages .message_action.block {
- background-image: url(block_button.png);
- color: #c1c1c1 !important;
-}
-
-.ffz-dark .messages div.message {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .messages div.message:last-of-type { border-bottom-color: transparent; }
-
-.ffz-dark .messages div.preview {
- background-color: rgb(16,16,16);
- border-bottom-color: rgba(255,255,255,0.2);
- border-left-color: transparent;
-}
-
-.ffz-dark .messages div.preview.unread {
- background-color: rgb(32,32,32);
- border-left-color: #6441a5;
-}
-
-.ffz-dark .messages div.preview:hover {
- background-color: rgb(8,8,8);
-}
-
-.ffz-dark .messages #messages_column {
- background-color: transparent;
-}
-
-.ffz-dark .page_links em {
- background-color: #EEE;
- color: #000;
-}
-
-.ffz-dark .page_links a.disabled.previous_page b,
-.ffz-dark .page_links span.disabled.previous_page b { border-right-color: transparent; }
-
-.ffz-dark .page_links a.disabled.next_page b,
-.ffz-dark .page_links span.disabled.next_page b { border-left-color: transparent; }
-
-/* Tshirts are weird */
-
-.ffz-dark .teespring-panel-progress {
- background-color: #393939;
-}
-
-.ffz-dark .teespring-panel-image a {
- cursor: pointer;
-}
-
-.ffz-dark .teespring-panel-image img {
- filter: drop-shadow(0px 0px 10px white);
- -moz-filter: drop-shadow(0px 0px 10px white);
- -webkit-filter: drop-shadow(0px 0px 10px white);
-}
-
-.ffz-dark .teespring-panel {
- cursor: inherit;
- background-color: rgb(8,8,8);
- border-color: rgba(255,255,255,0.2);
-}
-
-/* Settings */
-
-.ffz-dark .app_title { color: #ccc; }
-
-.ffz-dark .connect_items .connect-item-info .details-toggle,
-.ffz-dark .connect_items .connect-item-info,
-.ffz-dark ul.manage_simple span.obj {
- background-color: rgb(8,8,8);
-}
-
-.ffz-dark .connect_items .connect-item-info .details-toggle {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .connect_items .connect-item-info .details-toggle:hover {
- border-color: rgba(255,255,255,0.4);
-}
-
-.ffz-dark .connect_items .connect-item-info .status,
-.ffz-dark #settings #followers_content .hdr,
-.ffz-dark table.simple_table th {
- background-color: rgb(32,32,32);
- border-color: transparent;
-}
-
-.ffz-dark .multi_select li form:hover,
-.ffz-dark .multi_select li .ms-int:hover,
-.ffz-dark .multi_select li label:hover {
- background-color: rgb(8,8,8);
-}
-
-.ffz-dark #turbo_chat_color,
-.ffz-dark #turbo_emote_set {
- background-color: rgba(255,255,255,0.05);
-}
-
-.ffz-dark .ui-slider-horizontal:not(.player-slider) {
- background-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .subscription-dash .list-head th {
- border-color: #474747;
- color: #ccc;
-}
-
-.ffz-dark .section ul,
-.ffz-dark .section ul li,
-.ffz-dark .subdash-feature-section,
-.ffz-dark .subdash-feature-header__back,
-.ffz-dark .subdash-feature-header,
-.ffz-dark .subdash-feature-row--border,
-.ffz-dark .cl-container .section-header .header-content.indented,
-.ffz-dark .cl-container .section-header .header-back {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .subdash-feature-header__back:hover,
-.ffz-dark .cl-container .section-header .header-back:hover {
- background-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .new-dashboard .dashboard-section .submit-button-fieldset,
-.ffz-dark .subdash-feature-header,
-.ffz-dark .cl-container .section-header,
-.ffz-dark .cl-container .cl-section .cl-subheader {
- background-color: rgb(32,32,32);
-}
-
-.ffz-dark .cl-container .cl-section {
- background-color: rgb(16,16,16);
-}
-
-.ffz-dark fieldset {
- border-color: transparent;
-}
-
-.ffz-dark .connect_items .connect-item-details,
-.ffz-dark .connect_items .connect-item-details-legal,
-.ffz-dark .connect_items,
-.ffz-dark .premium_setup_contain #turbo_setup,
-.ffz-dark table.simple_table td,
-.ffz-dark table.simple_table th,
-.ffz-dark .cl-container .section-header,
-.ffz-dark .cl-container fieldset {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .cl-container fieldset .cl-input-container label { color: #fff }
-
-.ffz-dark .subdash-feature-row__title,
-.ffz-dark .cl-container fieldset .label-wrapper label {
- color: #ccc;
-}
-
-.ffz-dark .subdash-feature,
-.ffz-dark #emoticons #emote_switch .set-button,
-.ffz-dark #example .line {
- background-color: #19191f;
- color: #acacbf;
-}
-
-/* Dashboard */
-
-.ffz-dark .dash-tabs__item--active .dash-tabs__label:after {
- border-color: #191919;
-}
-
-.ffz-dark .dash-tabs__item {
- background-color: #151515;
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .dash-tabs__item--active { background-color: #191919 }
-
-.ffz-dark .game-details__carousel-wrapper:hover .game-details__carousel-arrow,
-.ffz-dark .card-carousel__button,
-.ffz-dark .carousel__button {
- background-color: #101010;
-}
-
-.ffz-dark .game-details__carousel-wrapper:hover .game-details__carousel-arrow:before,
-.ffz-dark .card-carousel__arrow:before,
-.ffz-dark .carousel__arrow:before {
- border-color: #a68ed2;
-}
-
-.ffz-dark .card-carousel__button:hover .card-carousel__arrow:before
-.ffz-dark .carousel__button:hover .carousel__arrow:before {
- border-color: #fff;
-}
-
-.ffz-dark .card .brick,
-.ffz-dark .brick.brick--theme-grey,
-.ffz-dark .card-carousel__button:hover
-.ffz-dark .carousel__button:hover {
- background-color: #191919;
-}
-
-.ffz-dark .brick {
- background-color: #121212;
-}
-
-.ffz-dark .dash-brick,
-.ffz-dark .dash-tabs__item--active .dash-tabs__label,
-.ffz-dark .ct-type-grey { color: #ccc }
-
-.ffz-dark .brick--marked.brick--theme-white,
-.ffz-dark .brick--marked.brick--theme-grey {
- box-shadow: 3px 0 0 #9a7fcc inset,0 0 0 1px rgba(255,255,255,0.2) inset;
-}
-
-.ffz-dark .brick--faint,
-.ffz-dark .brick--block {
- background-color: rgb(25,25,25);
-}
-
-.ffz-dark .brick,
-.ffz-dark .brick--faint,
-.ffz-dark .brick--block,
-.ffz-dark #action_feed .action,
-.ffz-dark .revHeader__item {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #mantle_skin .what { background-color: #6441a5; }
-.ffz-dark #action_feed .action { padding-bottom: 20px }
-
-.ffz-dark .js-stream-key-button-container .button {
- margin: 15px auto;
-}
-
-.ffz-dark .js-show-stream-key,
-.ffz-dark .js-reset-stream-key {
- display: block;
- margin: 15px auto;
- padding: 0;
- text-align: center;
- width: 100px;
- color: #fff !important;
-}
-
-.ffz-dark .js-show-stream-key { background-color: #804400; }
-.ffz-dark .js-reset-stream-key { border-color: rgb(128,0,0); }
-
-.ffz-dark .js-show-stream-key:hover,
-.ffz-dark .js-show-stream-key:focus,
-.ffz-dark .js-reset-stream-key:hover,
-.ffz-dark .js-reset-stream-key:focus {
- padding: 5px;
- margin: 10px auto;
-}
-
-.ffz-dark .stream-key {
- background: #000;
- color: #f2f2f2;
-}
-
-.ffz-dark .js-show-stream-key:hover,
-.ffz-dark .js-show-stream-key:focus { background-color: rgb(192,102,0); }
-
-.ffz-dark .js-reset-stream-key:hover,
-.ffz-dark .js-reset-stream-key:focus { background-color: rgb(192,0,0); }
-
-.ffz-dark .statchart_aggregator .statchart_first_column {
- background-color: rgb(32,32,32);
- color: #ccc;
- text-shadow: none;
-}
-
-.ffz-dark .statchart_aggregator {
- margin-top: 10px;
-}
-
-.ffz-dark .statchart_aggregator td {
- border-color: #3f3f42;
-}
-
-.ffz-dark .statchart_aggregator .statchart_first_column[style="color: #6441A5"] {
- color: #a68ed2 !important;
-}
-
-.ffz-dark .whatisthis {
- box-shadow: 0 0 0 1px rgba(255,255,255,0.2);
-}
-
-.ffz-dark .whatisthis:before {
- border-top-color: #101010;
-}
-
-.ffz-dark .whatisthis:after {
- border-top-color: #32323e;
-}
-
-.ffz-dark #chart_container svg rect[fill="#FFFFFF"] {
- fill: rgb(32,32,32) !important;
-}
-
-.ffz-dark #chart_container svg rect[fill="rgb(255,255,255)"] {
- fill: rgb(8,8,8) !important;
-}
-
-.ffz-dark #chart_container svg text[x="5"] {
- color: #fff !important;
- fill: #fff !important;
-}
-
-.ffz-dark #chart_container svg text.highcharts-title {
- fill: #a68ed2 !important;
-}
-
-.ffz-dark #chart_container svg text {
- fill: #fff;
-}
-
-.ffz-dark ul.subtabs li.selected {
- background-color: #333;
- border-radius: 4px;
-}
-
-.ffz-dark .dropmenu .menu-divider {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #header_logo svg path {
- fill: #fff;
-}
-
-.ffz-dark #dash_main .action .content .data,
-.ffz-dark #site_header {
- background-color: rgb(32,32,32);
-}
-
-.ffz-dark .header_divider {
- border-right-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #dash_main .dash-player-contain.collapsed #player_overlay,
-.ffz-dark .dash-hostmode-contain {
- background: #202020;
-}
-
-.ffz-dark #dash_main #delay_controls,
-.ffz-dark #dash_main #commercial_buttons,
-.ffz-dark .dash-hostmode-list-contain {
- border-top-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark #dash_main #controls_column .section_header {
- color: #ccc;
-}
-
-.ffz-dark .main,
-.ffz-dark .fullwidth_main {
- color: #ccc;
- background-color: transparent;
-}
-
-.ffz-dark .following-col .col-header,
-.ffz-dark .following-col .header,
-.ffz-dark .whatisthis .actions .divider,
-.ffz-dark .dash-chat-column {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .dash-column { border: none }
-.ffz-dark .js-dashboard-chat-module > div { border-right: none !important }
-
-
-.ffz-dark .vod-vertical-nav {
- background-color: #191919;
- border-color: #474747;
-}
-
-
-.ffz-dark .vod-vertical-nav__list-item .active,
-.ffz-dark .vod-vertical-nav__list-item .active:hover,
-.ffz-dark .vod-vertical-nav__list-item .active:active,
-.ffz-dark .vod-vertical-nav__list-item .active:focus {
- background-color: #6441a5;
-}
-
-.ffz-dark .vod-vertical-nav__link:hover,
-.ffz-dark .vod-vertical-nav__link:active,
-.ffz-dark .vod-vertical-nav__link:focus {
- background-color: #392e5c;
-}
-
-.ffz-dark .dashboard-page div[style*="background"][style*="#f9f8fc"] {
- background-color: #191919 !important
-}
-
-
-/* Front Page: Pulse */
-
-.ffz-dark .newsfeed {
- background-color: #101010;
-}
-
-
-
-/* /p/ Pages */
-
-.ffz-dark .header-announcement__link,
-.ffz-dark .front-page-announcement__link { color: inherit }
-
-.ffz-dark .header-announcement {
- color: #fff;
-}
-
-.ffz-dark #mantle_skin .wrapper {
- background-color: transparent;
-}
-
-.ffz-dark #mantle_skin div[style^=" display:background:#ffffff"],
-.ffz-dark #mantle_skin .social_links img {
- background-color: #fff;
-}
-
-.ffz-dark #mantle_skin div[style^=" display:background:#ffffff"] .extraspace { color: #333 }
-
-.ffz-dark #mantle_skin ul.vtabs li .not_linked,
-.ffz-dark #mantle_skin ul.vtabs li a {
- color: #acacbf;
-}
-
-.ffz-dark #partner_application .partner_reqs {
- background-color: #191919;
- border-color: #404040;
-}
-
-.ffz-dark #mantle_skin ul.vtabs li.selected a,
-.ffz-dark #mantle_skin ul.submenu li a:hover,
-.ffz-dark .sort-contain .sort-options li a:hover,
-.ffz-dark #mantle_skin ul.submenu .active a {
- color: #fff !important;
-}
-
-.ffz-dark #mantle_skin ul.vtabs li a:hover {
- color: rgb(222,222,222);
- background-color: transparent;
-}
-
-.ffz-dark #mantle_skin .press_list .callout {
- color: #acacbf;
- border-left-color: #575757;
-}
-
-.ffz-dark .legal_page ol.legal li {
- color: rgb(222,222,222);
-}
-
-.ffz-dark .static-page h1 {
- color: #fff !important;
- -webkit-text-stroke: none !important;
-}
-
-.ffz-dark .static-page p.p2 {
- color: #ccc;
- -webkit-text-stroke: none;
-}
-
-.ffz-dark .static-page td,
-.ffz-dark .static-page td {
- border-color: #474747;
-}
-
-.ffz-dark .legal_page ol.legal li p {
- color: #ccc;
-}
-
-/* Search */
-
-.ffz-dark ul.subtabs,
-.ffz-dark .user_list .user,
-.ffz-dark .mixed_list .user,
-.ffz-dark .video_list .video,
-.ffz-dark .mixed_list .video {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .user_list .user .user_stats .followers_count,
-.ffz-dark .mixed_list .user .user_stats .followers_count,
-.ffz-dark .clmgr-meta-special figure,
-.ffz-dark li.video.vods img.video_type {
- filter: invert(100%);
- -webkit-filter: invert(100%);
-}
-
-.ffz-dark .form__input[type=range] {
- background-color: transparent;
-}
-
-.ffz-dark .form__input[type=text],
-.ffz-dark .form__input[type=email],
-.ffz-dark .form__input[type=search],
-.ffz-dark select.pl-form__input,
-.ffz-dark textarea.form__input,
-.ffz-dark select.form__input,
-.ffz-dark .new-header-search input {
- color: #fff;
- background-color: rgba(255,255,255,0.05);
- box-shadow: rgba(255,255,255,0.2) 0 0 0 1px inset;
-}
-
-.ffz-dark .form__input[type=text]:focus,
-.ffz-dark .form__input[type=email]:focus,
-.ffz-dark .form__input[type=search]:focus,
-.ffz-dark textarea.form__input:focus,
-.ffz-dark select.form__input:focus,
-.ffz-dark .new-header-search input:focus {
- box-shadow: rgba(255,255,255,0.4) 0 0 0 1px inset;
-}
-
-/* Playlist */
-
-.ffz-dark .cn-chat-replay-header {
- background: transparent;
- border: none;
-}
-
-.ffz-dark .cn-chat-replay-header,
-.ffz-dark .ember-chat .chat-header {
- box-shadow: inset 0 -1px 0 0 rgba(255,255,255,0.2);
-}
-
-.ffz-dark .playlist-controller,
-.ffz-dark .playlist-item {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .playlist-container:not(.playlist-enabled) .playlist-item:hover,
-.ffz-dark .playlist-container:not(.playlist-enabled) .ui-sortable-helper {
- background-color: rgba(255,255,255,0.2);
-}
-
-
-/* VoDs */
-
-.ffz-dark .app-main .chatReplay .notice-wrapper .svg-logo_glitch {
- fill: #242424 !important;
-}
-
-.ffz-dark .app-main .chatReplay.dark,
-.ffz-dark .app-main .chatReplay .noticeWrap,
-.ffz-dark .app-main .chatReplay .notice-wrapper {
- background-color: #19191f;
-}
-
-.ffz-dark.ffz-no-blue .app-main .chatReplay.dark,
-.ffz-dark.ffz-no-blue .app-main .chatReplay .noticeWrap,
-.ffz-dark.ffz-no-blue .app-main .chatReplay .notice-wrapper {
- background-color: #191919;
-}
-
-.ffz-dark .chatReplay .loading-spinner-container {
- background: rgba(25,25,25,0.65);
-}
-
-
-/* Conversations */
-
-.ffz-dark .conversations-list-bottom-bar {
- background-color: #19191f;
- color: #8c8c9c;
- border-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .conversation-list-bottom-bar:hover {
- color: #fff;
-}
-
-.ffz-dark .conversations-list {
- background-color: #19191f;
- border: 1px solid #32323e;
- color: #fff;
-}
-
-.ffz-dark .conversations-list:before {
- right: 9px;
- border-color: rgba(50,50,62,0);
- border-top-color: #32323e;
-}
-
-.ffz-dark .conversations-list:after {
- border-color: rgba(25,25,31,0);
- border-top-color: #19191f;
- border-width: 10px;
- margin-left: -10px;
-}
-
-.ffz-dark .conversations-list .conversations-list-header {
- background-color: #121217;
- border-bottom: 1px solid #32323e;
- color: #fff;
-}
-
-.ffz-dark .conversations-list .conversation-preview-line {
- color: #8c8c9c;
-}
-
-.ffz-dark .conversations-list .search-divider,
-.ffz-dark .conversations-list .conversations-list-item {
- border-bottom: 1px solid #32323e;
-}
-
-.ffz-dark .conversations-list .conversations-list-item:hover {
- background-color: #121217;
-}
-
-.ffz-dark .conversation-window {
- background-color: #19191f;
- box-shadow: none;
- color: #8c8c9c;
-}
-
-.ffz-dark .conversations-list .search-divider,
-.ffz-dark .convoHeader {
- background-color: #121217;
- box-shadow: none;
-}
-
-.ffz-dark .conversation-input-actions .button,
-.ffz-dark .conversation-input-actions .follow-button:not(.ember-follow) .follow,
-.ffz-dark .follow-button:not(.ember-follow) .conversation-input-actions .follow {
- background-color: #444;
-}
-
-.ffz-dark .conversation-window.has-focus .convoHeader {
- background-color: #121217;
-}
-
-.ffz-dark .conversation-window.has-focus .convoHeader .username {
- color: #fff;
-}
-
-.ffz-dark .conversation-window.has-focus .conversation-input-bar textarea:focus {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .conversation-window.has-focus .conversation-input-actions .button,
-.ffz-dark .conversation-window.has-focus .conversation-input-actions .follow-button:not(.ember-follow) .follow,
-.ffz-dark .follow-button:not(.ember-follow) .conversation-window.has-focus .conversation-input-actions .follow {
- background-color: #6441a5;
-}
-
-.ffz-dark .conversation-system-message {
- background-color: #19191f;
- color: #8c8c9c;
- border-bottom: 1px solid #32323e;
-}
-
-.ffz-dark .conversation-input-bar .emoticon-selector-toggle svg path {
- fill: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .conversation-input-bar .emoticon-selector-toggle:hover svg path {
- fill: rgba(255,255,255,0.5);
-}
-
-.ffz-dark .emoticon-selector-box .emote-set {
- border-color: #323232 !important;
-}
-
-.ffz-dark .conversation-input-bar .emoticon-selector-box .emoticon-grid {
- background-color: #191919;
-}
-
-.ffz-dark .ember-chat .chat-settings .experimental-options {
- border-top-color: rgba(255,255,255, 0.2);
-}
-
-.ffz-dark .dash-widget__divider,
-.ffz-dark .conversation-settings-menu .options-divider {
- border-bottom-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .conversation-settings-menu:before {
- border-color: transparent;
- border-bottom-color: #32323e;
-}
-
-.ffz-dark .conversation-settings-menu:after {
- border-color: transparent;
- border-bottom-color: rgb(16,16,16);
-}
-
-.ffz-dark .conversation-window {
- border: 1px solid rgba(255,255,255,0.2);
-}
-
-.ffz-dark:not(.ffz-top-conversations) .conversation-window {
- border-bottom: none;
-}
-
-.ffz-dark.ffz-top-conversations .conversation-window {
- border-top: none;
-}
-
-.ffz-dark .conversation-window:not(.collapsed) .convoHeader {
- border-bottom: 1px solid rgba(255,255,255,0.2);
-}
-
-
-/* Creative UI */
-
-.ffz-dark .ct-spotlight__right {
- background-color: #121212;
-}
-
-.ffz-dark .ct-spotlight__controls-container {
- background-color: #161616;
- box-shadow: -1px 0 0 #303030 inset;
-}
-
-.ffz-dark .ct-bar {
- background-color: #121212;
- box-shadow:
- 0 2px 6px -2px rgba(255,255,255,0.1),
- 0 1px 0 rgba(255,255,255,0.05),
- 0 -1px 0 rgba(255,255,255,0.05);
-}
-
-.ffz-dark .ct-banner__name,
-.ffz-dark .ct-banner__status,
-.ffz-dark .ct-type-2,
-.ffz-dark .ct-type-4 {
- color: #aaa;
-}
-
-.ffz-dark .ct-type-grey-light {
- color: #ccc;
-}
-
-
-.ffz-dark .ct-crumb:after {
- box-shadow: 12px -6px 24px -8px rgba(255,255,255,0.1);
- border-color: #303030;
-}
-
-.ffz-dark .ct-spotlight__avatar {
- box-shadow: 0 0 0 1px #303030, 0 2px 3px #303030;
-}
-
-.ffz-dark .ct-spotlight__right--col .ct-spotlight__card-container {
- box-shadow: 0 -1px 0 #303030 inset;
-}
-
-.ffz-dark hr,
-.ffz-dark .ct-spotlight__section,
-.ffz-dark .balloon__stroke,
-.ffz-dark .ct-spotlight__avatar,
-.ffz-dark .ct-bar__item {
- border-color: #303030;
-}
-
-.ffz-dark .ct-crumb--1:after,
-.ffz-dark .ct-crumb--1 .ct-crumb__label {
- background-color: #121212;
-}
-
-.ffz-dark .ct-crumb--2:after,
-.ffz-dark .ct-crumb--2 .ct-crumb__label {
- background-color: #161616;
-}
-
-
-/* Creative Tags */
-
-.ffz-dark .ct-tags__tag {
- background-color: #121212;
- color: #999 !important;
-}
-
-.ffz-dark .ct-tag--light .ct-tag__link:hover { color: #ccc !important }
-
-.ffz-dark .ct-tags__tag:hover {
- background-color: #000;
- color: #ccc !important;
-}
-
-.ffz-dark .ct-tags__tag.ct-tags__tag--current {
- background-color: #7d5bbc;
- color: #fff !important;
-}
-
-.ffz-dark .ct-banner--off .ct-banner__header { color: #999 }
-.ffz-dark .ct-banner--off .ct-banner__link { color: #ccc }
-
-
-/* Activity Feeds */
-
-.button.alert { color: #fff !important }
-
-.ffz-dark .activity-notice { background-color: rgba(24,24,24,0.9) }
-.ffz-dark .pill { background-color: rgba(255,255,255,0.2) }
-
-.ffz-dark .activity-list-end svg { fill: #474747 }
-
-.ffz-dark .activity-meta {
- box-shadow: 0 1px 0 #474747;
-}
-
-.ffz-dark .activity-meta-divider:before {
- background-color: #191919;
- border-top-color: #474747;
- border-left-color: #474747;
-}
-
-.ffz-dark .activity-button:hover {
- border-color: #d5d5d5;
- background-color: #242424;
-}
-
-.ffz-dark .activity-create:not(.activity-create--expand) { border-color: #474747 }
-
-.ffz-dark .activity-create__actions {
- background-color: #191919;
- border-color: #474747;
- box-shadow: none;
-}
-
-.ffz-dark .activity-create__container,
-.ffz-dark .activity-button {
- border-color: #474747;
- color: #fff !important;
- background-color: #191919;
-}
-
-.ffz-dark .activity-card {
- border: 1px solid;
- background-color: #191919;
-}
-
-.ffz-dark .activity-card__reason {
- background-color: #141414;
- color: #ccc;
- box-shadow: none;
-}
-
-.ffz-dark .list-load-more,
-.ffz-dark .activity-card {
- border-color: #474747;
-}
-
-.ffz-dark .activity-card__status {
- background-color: #191919;
-}
-
-.ffz-dark .activity-meta-divider {
- box-shadow: inset 0 -1px 0 #474747;
-}
-
-.ffz-dark a.balloon__link:hover { color: #fff !important }
-.ffz-dark .activity-meta__name { color: #ccc }
-
-
-.ffz-dark .activity-reaction__emote-contain {
- background-color: #101010;
-}
-
-
-.ffz-dark .activity-comments {
- border-color: #474747;
-}
-
-.ffz-dark .activity-comment:hover {
- background-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .activity-card__comments {
- background-color: #121212;
- box-shadow:inset 0 1px 0 #474747;
-}
-
-.ffz-dark .activity-create {
- background-color: #101010;
- box-shadow: none; /*inset 0 0 0 1px #474747;*/
-}
-
-.ffz-dark .activity-create--card {
- box-shadow: 0 1px 2px rgba(255,255,255,0.2);
-}
-
-.ffz-dark .c-background { background: #101010 !important }
-
-.ffz-dark .activity-create--focus {
- background-color: #191919;
- /*box-shadow: none; inset 0 0 0 1px #474747;*/
- border-color: rgba(255,255,255,0.4) !important;
-}
-
-.ffz-dark .c-text { color: #aaa !important }
-.ffz-dark .c-text-alt { color: #999 !important }
-
-.ffz-dark .activity-add-comment__textarea:before {
- background: #1d1d1d;
- border-left-color: #474747;
- border-bottom-color: #474747;
-}
-
-.ffz-dark .mod-dashboard__video-title,
-.ffz-dark .activity-card { color: #ccc }
-
-.inherit-color { color: inherit !important }
-
-.ffz-dark .tw-button--icon-only figure svg {
- fill: #a68cd4;
-}
-
-.ffz-dark .mod-comment {
- background-color: #191919;
- box-shadow: inset 0 0 0 1px #474747;
-}
-
-.ffz-dark .mod-comment__timestamp {
- color: #a68cd4;
- background-color: #222;
-}
-
-.ffz-dark .mod-comment__video a:active,
-.ffz-dark .mod-comment__video a:focus,
-.ffz-dark .mod-comment__video a:hover { background-color: #333 !important }
-
-.ffz-dark .mod-comment:active,
-.ffz-dark .mod-comment:focus,
-.ffz-dark .mod-comment:hover {
- box-shadow: 0 2px 2px 0 rgba(255,255,255,.05), inset 0 0 0 1px #474747;
-}
-
-.ffz-dark .c-background-alt-2 {
- background-color: #222 !important
-}
-
-.ffz-dark .automod-words__item {
- border-color: #474747;
- background-color: #111;
-}
-
-.ffz-dark .automod-words__item--selected {
- background-color: #333;
-}
-
-.ffz-dark .automod-words__text { color: #ccc }
-
-.ffz-dark .automod-words__fade-mask {
- background: none;
-}
-
-/* VOD Messages */
-
-.ffz-dark .vod-chat {
- color: #ccc;
- background-color: #191919;
-}
-
-.ffz-dark .vod-chat__header {
- box-shadow: inset 0 -1px 0 0 #474747;
-}
-
-.ffz-dark .vod-message.vod-message--focused .vod-message__reply,
-.ffz-dark .vod-message:hover .vod-message__reply,
-.ffz-dark .vod-message__reply {
- box-shadow: inset 3px 0 0 0 #474747;
-}
-
-.ffz-dark .vod-message__reply-button {
- color: #a68cd4;
-}
-
-.ffz-dark .vod-chat__input {
- background-color: #191919;
- box-shadow: inset 0 1px 0 0 #474747;
-}
-
-.ffz-dark .vod-message.vod-message--focused,
-.ffz-dark .vod-message:hover {
- background-color: #333;
-}
-
-/* Search Panel */
-
-.ffz-dark[data-current-path="user.channel.index.index"] .search-panel { background-color: rgba(16,16,16,0.9) }
-
-.ffz-dark .search-panel {
- background-color: #101010;
- color: #C3C3C3;
-}
-
-.ffz-dark .search-panel--fly:before,
-.ffz-dark .search-panel--fly:after {
- border-bottom-color: #404040;
-}
-
-.ffz-dark .search-panel__title-bar {
- background-color: #090909;
-}
-
-.ffz-dark .search-panel,
-.ffz-dark .search-panel__title-bar,
-.ffz-dark .search-panel__title-back {
- border-color: rgba(255,255,255,0.2)
-}
-
-.ffz-dark .search-panel__title { color: #999 }
-
-.ffz-dark .search-panel__title-back:hover,
-.ffz-dark .search-result-view__block.isActive { background-color: #222 }
-
-.ffz-dark .search-result-view__titlesep:hover,
-.ffz-dark a:hover .card__title,
-.ffz-dark .card__title a:hover,
-.ffz-dark .card__info a:hover {
- color: #ccd;
-}
-
-.ffz-dark .search-result-view__block .card__info span,
-.ffz-dark .search-result-view__block .card__title,
-.ffz-dark .search-result-view__title-more { color: #a68ed2 !important }
-.ffz-dark .search-result-view__titlesep {
- background-color: #090909;
-}
-
-.ffz-dark .search-result-view__block .card__layout:hover .card__info span,
-.ffz-dark .search-result-view__block .card__layout:hover .card__title {
- color: #fff !important
-}
-
-
-/* Channel Redesign */
-
-.ffz-dark .cn-bar {
- background-color: #101010;
- box-shadow: inset 0 -1px 0 #161616, -1px 1px rgba(255,255,255,0.065);
-}
-
-.ffz-dark .twitchbot-settings__border-more,
-.ffz-dark .border-t,
-.ffz-dark .border-b,
-.ffz-dark .cn-metabar__more {
- border-color: rgba(255,255,255,0.2) !important;
-}
-
-.ffz-dark .twitchbot-settings__hover-text-help,
-.ffz-dark .twitchbot-settings__rule-container:hover,
-.ffz-dark .twitchbot-settings__right-container {
- background-color: #191919;
-}
-
-
-.ffz-dark .cn-bar__button--resubscribe,
-.ffz-dark .button--icon.cn-bar__button--subscribed {
- background-color: #161616;
- box-shadow: 0 0 0 1px rgba(255,255,255,0.065) inset;
-}
-
-
-.ffz-dark .twitchbot-settings__help,
-.ffz-dark .twitchbot-settings__level-context,
-.ffz-dark .cn-bar__avatar-wrap {
- background-color: #101010;
- border-color: #101010;
-}
-
-.ffz-dark .twitchbot-settings__arrow-up {
- border-bottom-color: #101010;
-}
-
-.ffz-dark .cn-bar__displayname {
- color: #c3c3c3;
-}
-
-.ffz-dark .cn-tabs__item--withseparator:after {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .cn-tabs__item > a {
- color: #999;
-}
-
-.ffz-dark .cn-tabs__item > a.active,
-.ffz-dark .cn-tabs__item > a:hover {
- color: #a68ed2;
-}
-
-.ffz-dark .cn-tabs__item > a:hover:after {
- border-color: #a68ed2;
-}
-
-.ffz-dark .offer-list__core:after,
-.ffz-dark .offer-item__ci:after {
- background-image: linear-gradient(rgba(16,16,16,0) 0,#101010 100%)
-}
-
-/* Video Uploads */
-
-.ffz-dark .vod-vertical-nav + div .card > div[style],
-.ffz-dark #video-manager .card > div[style],
-.ffz-dark .videos.uploads .card > div[style] {
- background-color: #101010 !important;
-}
-
-.ffz-dark .cn-vod-info__divider { background-color: rgba(255,255,255,0.2) }
-.ffz-dark .card__body--theme-grey {
- background-color: #161616;
-}
-
-.ffz-dark .drop-zone__border,
-.ffz-dark .subscription-modal__right,
-.ffz-dark .subscription-modal__bar {
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .subscription-modal__sub-tabs input:checked+.subscription-modal__sub-tabs-label {
- color: #ccc;
- box-shadow: 0 -2px 0 #a68ed2 inset, 0 4px 6px -4px #a68cd4;
-}
-
-.ffz-dark .subscription-modal__sub-tabs-label {
- color: #a68cd4;
-}
-
-.ffz-dark .balloon .filter-bar__balloon-link--active {
- color: #fff !important;
- background-color: #6441a5 !important;
-}
-
-
-/* New Dashboard */
-
-.ffz-dark .gu-transit,
-.ffz-dark .dash-widget {
- background-color: #191919;
- border-color: rgba(255,255,255,0.2);
- box-shadow: 0 2px 2px 0 rgba(255,255,255,0.05);
- color: #c3c3c3;
-}
-
-.ffz-dark .form__input[type=checkbox]:not(:checked)+label:before,
-.ffz-dark .form__input[type=radio]:not(:checked)+label:before,
-.ffz-dark .dash-widget__header {
- background-color: #232323;
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .dash-widget-watchparty-card:hover {
- background-color: rgba(255,255,255,0.1);
-}
-
-
-/* Notification Center */
-
-.ffz-dark .notification-center {
- background-color: #101010;
- color: #C3C3C3;
- box-shadow: 0 0 0 1px rgba(255,255,255,0.2);
-}
-
-.ffz-dark .notification-center-balloon:before,
-.ffz-dark .notification-center-balloon:after { display: none }
-
-.ffz-dark .friend-requests__empty,
-.ffz-dark .friend-request__content-msg,
-.ffz-dark .friend-requests__footer a:hover,
-.ffz-dark .notification-center__unread-container,
-.ffz-dark .notification-center__subheader-text,
-.ffz-dark .notification__content-msg,
-.ffz-dark .notification-center__header-text { color: #C3C3C3 }
-.ffz-dark .notification__details { color: #777 }
-
-.ffz-dark .notification__dismiss svg,
-.ffz-dark .notification__details svg { fill: #777 }
-
-.ffz-dark .notification__dismiss:hover svg { fill: #AAA }
-.ffz-dark .notification__dismiss svg polygon { fill: #333 }
-
-.ffz-dark .notification-center__subheader {
- background-color: #1a1a1a;
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .sc-onboarding .sc-onboarding__content,
-.ffz-dark .sc-onboarding .sc-onboarding__close {
- background-color: #101010;
- box-shadow: -0.1rem 0 0 #474747 inset;
-}
-
-.ffz-dark .sc-onboarding {
- box-shadow: 0 0 0 0.1rem #474747;
-}
-
-.ffz-dark .offer-item__ci--wrap { border-color: #474747 }
-
-.ffz-dark .offer-item__background--purple,
-.ffz-dark .offer-item--title,
-.ffz-dark .notification-center__footer,
-.ffz-dark .notification-center__header {
- border-color: rgba(255,255,255,0.2);
- background-color: #101010;
-}
-
-.ffz-dark .offer-item__background--gray,
-.ffz-dark .notification-center__core {
- background-color: rgba(255,255,255,0.05);
-}
-
-.ffz-dark .notification-center__core-end {
- box-shadow: inset 0.1rem 0.4rem 1rem -.5rem;
- color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .friend-requests__empty,
-.ffz-dark .friend-requests__footer,
-.ffz-dark .notification--read,
-.ffz-dark .notification {
- background-color: rgba(255,255,255,0.01);
- border-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .notification--unread {
- background-color: rgba(255,255,255,0.05);
- border-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .friend-requests__footer:hover,
-.ffz-dark .notification--read:hover,
-.ffz-dark .notification--unread:hover {
- background-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .friend-requests__footer a {
- color: #a68ed2;
-}
-
-/* Playlists are Stupid */
-
-.ffz-dark .playlist-card__title { color: #ccc }
-.ffz-dark .playlist-card-meta__stat { color: #a68cd4 }
-
-.ffz-dark .playlist-add-search-card__layout {
- border-color: transparent !important;
- box-shadow: none !important;
-}
-
-.ffz-dark .playlist-add-search-card--selected:before {
- background-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .playlist-card {
- color: #ccc;
- background-color: #161616;
-}
-
-.ffz-dark .dash-widget-watchparty-card__gripper,
-.ffz-dark .playlist-editor-card__gripper {
- background-color: #242424;
- box-shadow: -8px 0 5px -5px #202020;
-}
-
-.ffz-dark img[src="https://static-cdn.jtvnw.net/ttv-playlists-thumbnails-prod/missing-video-thumb-640x360.png"] {
- filter: grayscale(100%) invert(100%);
-}
-
-/* Front Page is Stupid */
-
-.ffz-dark .fp-side {
- background-color: #191919;
- box-shadow: inset 0 0 0 1px #474747;
-}
-
-.ffz-dark .fp-side__footer {
- background-color: #121212;
- box-shadow:
- inset 0 -1px 0 #474747,
- inset -1px 0 0 #474747,
- inset 1px 0 0 #474747;
-}
-
-.ffz-dark .fp-carousel-nav__item:not(.fp-carousel-nav__item--active) {
- border-color: #474747;
-}
-
-.ffz-dark .fp-carousel__title,
-.ffz-dark .fp-carousel__desc p,
-.ffz-dark .fp-carousel__name,
-.ffz-dark .fp-side__header { color: #c3c3c3 }
-
-/* Game Pages */
-
-.ffz-dark .game-details-igc__details,
-.ffz-dark .game-details__section--white {
- background-color: #060606;
-}
-
-.ffz-dark .game-details__section--grey {
- background-color: #101010;
-}
-
-.ffz-dark .inventory-header,
-.ffz-dark .game-details-igc__card,
-.ffz-dark .cmrc-igc-line--with-separators,
-.ffz-dark .game-details__section--white+.game-details__section--grey,
-.ffz-dark .game-details__section--grey+.game-details__section--white {
- border-color: #474747;
-}
-
-.ffz-dark .icon-platform-windows--black svg {
- fill: #ccc;
-}
-
-.ffz-dark .activity--not-included { background-color: #202020; }
-
-.ffz-dark .activity--level-0 { background-color: rgba(100,65,164,.1) }
-.ffz-dark .activity--level-1 { background-color: rgba(100,65,164,.25) }
-.ffz-dark .activity--level-2 { background-color: rgba(100,65,164,.4) }
-.ffz-dark .activity--level-3 { background-color: rgba(100,65,164,.55) }
-.ffz-dark .activity--level-4 { background-color: rgba(100,65,164,.7) }
-.ffz-dark .activity--level-5 { background-color: rgba(100,65,164,.85) }
-.ffz-dark .activity--level-6 { background-color: rgba(100,65,164,1) }
-
-.ffz-dark .inventory-item__image {
- background-color: #191919;
- border-color: #474747;
-}
-
-.ffz-dark .border-r {
- border-color: #474747;
-}
-
-.ffz-dark .bg--grey {
- background-color: #242424;
-}
-
-.ffz-dark .clmgr-table__cell-wrapper:nth-child(even) .clmgr-table__cell--collapsed {
- background-color: rgba(255,255,255,0.1);
-}
-
-.ffz-dark .clmgr-table__cell-wrapper:nth-child(even) .clmgr-table__cell--collapsed:hover,
-.ffz-dark .clmgr-table__cell--collapsed:hover {
- background-color: rgba(255,255,255,0.2);
-}
-
-.ffz-dark .stats-table__header {
- background-color: #101010;
-}
-
-.ffz-dark .border,
-.ffz-dark .stats-table__cell,
-.ffz-dark .stats-table {
- border-color: #474747;
-}
-
-.ffz-dark .stats-table__cell { color: #ccc }
-
-.ffz-dark .stats-table__row:nth-child(even) {
- background-color: #191919;
-}
-
-.ffz-dark .stats-table__row:hover {
- background-color: #333;
-}
\ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
deleted file mode 100644
index a6a4b08c..00000000
--- a/gulpfile.js
+++ /dev/null
@@ -1,292 +0,0 @@
-// Dependencies
-var fs = require('fs'),
- gulp = require('gulp'),
- browserify = require('gulp-browserify'),
- header = require('gulp-header'),
- footer = require('gulp-footer'),
- concat = require('gulp-concat'),
- clean = require('gulp-clean'),
- util = require('gulp-util'),
- rename = require('gulp-rename'),
- uglify = require('gulp-uglify');
-
-// Templates
-var jsEscape = require('gulp-js-escape'),
- wrap = require('gulp-wrap'),
- declare = require('gulp-declare'),
- cleanCSS = require('gulp-clean-css');
-
-
-// LESS
-var less = require('gulp-less'),
- sourcemaps = require('gulp-sourcemaps');
-
-
-// Deploy Dependencies
-var ftp = require('vinyl-ftp'),
- request = require('request');
-
-
-// Server Dependencies
-var http = require("http"),
- https = require("https"),
- net = require('net'),
- path = require("path"),
- request = require("request"),
- url = require("url");
-
-var server_version = "0.1.1";
-
-
-// Tasks
-
-gulp.task('clean', function() {
- return gulp.src('build', {read:false})
- .pipe(clean());
-});
-
-gulp.task('prepare', ['clean'], function() {
- return gulp.src(['src/**/*'])
- .pipe(gulp.dest('build/'));
-});
-
-
-//gulp.task('templates', ['prepare'], function() {
-// return gulp.src(['build/templates/**/*.hbs'])
-// .pipe(jsEscape())
-// .pipe(wrap('Handlebars.compile(<%= contents %>)'))
-// .pipe(declare({
-// root: 'exports',
-// noRedeclare: true,
-// processName: function(filePath) {
-// var match = filePath.match(/build[\\\/]templates[\\\/](.*)\.hbs$/);
-// return declare.processNameByPath((match && match.length > 1) ? match[1] : filePath);
-// }
-// }))
-// .pipe(concat('templates.js'))
-// .pipe(gulp.dest('build/'))
-// .on('error', util.log);
-//});
-
-gulp.task('styles', ['prepare'], function() {
- //return;
- return gulp.src(['build/less/*.less', '!build/less/style.less'])
- .pipe(sourcemaps.init())
- .pipe(less())
- .pipe(sourcemaps.write())
- .pipe(gulp.dest(__dirname))
- .on('error', util.log);
-});
-
-
-gulp.task('embedded_styles', ['prepare'], function() {
- return gulp.src(['build/styles/**/*.css'])
- .pipe(cleanCSS())
- .pipe(jsEscape())
- .pipe(declare({
- root: 'exports',
- noRedeclare: true,
- processName: function(filePath) {
- var match = filePath.match(/build[\\\/]styles[\\\/](.*)\.css$/);
- return declare.processNameByPath((match && match.length > 1) ? match[1] : filePath);
- }
- }))
- .pipe(concat('compiled_styles.js'))
- .pipe(gulp.dest('build/'))
- .on('error', util.log)
-});
-
-
-gulp.task('scripts', ['embedded_styles'], function() {
- return gulp.src(['build/main.js'])
- .pipe(browserify())
- .pipe(concat('script.js'))
- .pipe(header('(function(window) {'))
- .pipe(footer(';window.ffz = new FrankerFaceZ()}(window));'))
- .pipe(gulp.dest(__dirname))
- .on('error', util.log);
-});
-
-
-gulp.task('watch', ['default', 'server'], function() {
- return gulp.watch('src/**/*', ['default']);
-});
-
-gulp.task('default', ['styles', 'scripts']);
-
-
-// Deploy
-
-gulp.task('minify_script', ['scripts'], function() {
- return gulp.src(['*.js', '!*.min.js', '!gulpfile.js'])
- .pipe(uglify())
- .pipe(rename(function(path) {
- path.basename += '.min';
- }))
- .pipe(gulp.dest(__dirname))
- .on('error', util.log);
-});
-
-gulp.task('minify_style', function() {
- return gulp.src(['*.css', '!*.min.css'])
- .pipe(cleanCSS())
- .pipe(rename(function(path) {
- path.basename += '.min';
- }))
- .pipe(gulp.dest(__dirname))
- .on('error', util.log);
-});
-
-gulp.task('minify', ['minify_script', 'minify_style']);
-
-
-gulp.task('upload', ['minify'], function() {
- // Load credentials from an external file.
- var contents = fs.readFileSync('credentials.json', 'utf8'),
- cred = JSON.parse(contents);
-
- cred.log = util.log;
-
- // Create the connection.
- var conn = ftp.create(cred);
-
- // What we're transfering.
- var ftp_path = cred.remote_path,
-
- globs = [
- "script.min.js",
- "style.min.css",
- "dark.min.css",
- "changelog.html"
- ];
-
- util.log(cred.remote_path);
-
- return gulp.src(globs, {base: '.', buffer: false})
- .pipe(conn.newerOrDifferentSize(ftp_path))
- .pipe(conn.dest(ftp_path))
- .on('error', util.log);
-});
-
-gulp.task('clear_cache', ['upload'], function(cb) {
- // Load credentials from an external file.
- var contents = fs.readFileSync('credentials.json', 'utf8'),
- cred = JSON.parse(contents);
-
- // Build the URLs.
- var base = "://cdn.frankerfacez.com/script/",
- files = [],
- globs = [
- "script.min.js",
- "style.min.css",
- "dark.min.css",
- "changelog.html"
- ];
-
- for(var i=0; i < globs.length; i++) {
- files.push("http" + base + globs[i]);
- files.push("https" + base + globs[i]);
- }
-
- request({
- method: 'DELETE',
- uri: "https://api.cloudflare.com/client/v4/zones/" + cred.cloudflare_zone + "/purge_cache",
- headers: {
- "X-Auth-Email": cred.cloudflare_email,
- "X-Auth-Key": cred.cloudflare_key
- },
- json: {
- "files": files
- }
- }, function(error, request, body) {
- if ( error )
- return util.log("[FAIL] Error: " + error);
- else if ( request.statusCode !== 200 )
- return util.log("[FAIL] Non-200 Status: " + request.statusCode);
-
- util.log("[SUCCESS] Cache cleared.");
- cb();
- });
-});
-
-gulp.task('deploy', ['upload', 'clear_cache']);
-
-
-// Server
-
-gulp.task('server', function() {
- var handle_req = function(req, res) {
- var uri = url.parse(req.url).pathname,
- lpath = path.join(uri).split(path.sep);
-
- if ( uri == "/dev_server" ) {
- util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.green("200") + " GET " + util.colors.magenta(uri));
- res.writeHead(200, {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"});
- return res.end(JSON.stringify({path: process.cwd(), version: server_version}));
- }
-
- if ( ! lpath[0] )
- lpath.shift();
-
- if ( lpath[0] == "script" )
- lpath.shift();
- else
- lpath.splice(0, 0, "cdn");
-
- var file = path.join(process.cwd(), lpath.join(path.sep));
-
- fs.exists(file, function(exists) {
- if ( ! exists ) {
- util.log("[" + util.colors.cyan("HTTP") + "] " + util.colors.bold.blue("CDN") + " GET " + util.colors.magenta(uri));
- return request.get("http://cdn.frankerfacez.com/" + uri).on('error', function(err) { res.end() }).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, 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, headers);
- fs.createReadStream(file).pipe(res);
- });
-
- };
-
- if ( fs.existsSync("dev_key.pem") ) {
- var https_options = {
- key: fs.readFileSync("dev_key.pem"),
- cert: fs.readFileSync("dev_cert.pem")
- };
-
- http.createServer(handle_req).listen(8001, "localhost");
- https.createServer(https_options, handle_req).listen(8002, "localhost");
-
- net.createServer(function(conn) {
- conn.on('error', function(e) {
- util.log("[" + util.colors.cyan("HTTP") + "] Connection Error: " + util.colors.magenta('' + e));
- });
-
- conn.once('data', function(buf) {
- var address = (buf[0] === 22) ? 8002 : 8001;
- var proxy = net.createConnection(address, function() {
- proxy.write(buf);
- conn.pipe(proxy).pipe(conn);
- });
- });
- }).listen(8000);
-
- util.log("[" + util.colors.cyan("HTTPS") + "] Listening on Port: " + util.colors.magenta("8000"));
-
- } else {
- http.createServer(handle_req).listen(8000, "localhost");
- util.log("[" + util.colors.cyan("HTTP") + "] Listening on Port: " + util.colors.magenta("8000"));
- }
-});
\ No newline at end of file
diff --git a/old_changes.html b/old_changes.html
index 06de919b..687d4547 100644
--- a/old_changes.html
+++ b/old_changes.html
@@ -1,3 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"});
- }
- }
-
- if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
- tokens = this.tokenize_emotes(user_id, room_id, tokens)
-
- return tokens;
-}
-
-
-FFZ.prototype.render_token = function(render_links, warn_links, render_bits, token) {
- if ( ! token )
- return "";
-
- if ( token.hidden || (this.settings.chat_rich_content && token.rich_removed) )
- return "";
-
- else if ( token.type === "raw" )
- return token.html;
-
- else if ( token.type === "user" )
- return '' + utils.sanitize(token.text) + ' ';
-
- else if ( token.type === "emoticon" ) {
- var src = token.imgSrc, srcset, cls, extra;
- if ( token.ffzEmote ) {
- var emote_set = this.emote_sets && this.emote_sets[token.ffzEmoteSet],
- emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote];
-
- srcset = emote ? emote.srcSet : token.srcSet;
- //extra = (emote ? ` data-ffz-emote="${emote.id}"` : '') + (emote_set ? ` data-ffz-set="${emote_set.id}"` : '');
- extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : '')
-
- } else if ( token.ffzEmoji ) {
- var setting = this.settings.parse_emoji;
- if ( setting === 0 || (setting === 1 && ! token.tw) || (setting === 2 && ! token.noto) || (setting === 3 && ! token.one) )
- return token.altText;
-
- src = setting === 3 ? token.one_src : (setting === 2 ? token.noto_src : token.tw_src);
- //extra = ` data-ffz-emoji="${token.ffzEmoji}" height="18px"`;
- extra = ' data-ffz-emoji="' + token.ffzEmoji + '" height="18px"';
- cls = ' emoji';
-
- } else {
- var id = FFZ.src_to_id(src),
- replacement = this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[id];
-
- //extra = ` data-emote="${id}" onerror="FrankerFaceZ._emote_mirror_swap(this)"`;
- extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"';
-
- if ( replacement ) {
- src = constants.EMOTE_REPLACEMENT_BASE + replacement;
- srcset = '';
- } else
- srcset = utils.build_srcset(id);
- }
-
- //return ` `;
- var f = this, prefix = '', suffix = '';
- if ( token.modifiers && token.modifiers.length ) {
- prefix = '';
- suffix = _.map(token.modifiers, function(t) {
- return '' + f.render_token(render_links, warn_links, render_bits, t) + ' ';
- }).join('') + ' ';
-
- extra += ' data-ffz-modifiers="' + utils.quote_attr(_.map(token.modifiers, function(t) { return t.ffzEmote }).join(' ')) + '" data-modifier-info="' + utils.quote_attr(JSON.stringify(_.map(token.modifiers, function(t) { return [t.ffzEmoteSet, t.ffzEmote] }))) + '"';
- }
-
- return prefix + ' ' + suffix;
- }
-
- else if ( token.type === "tag" ) {
- var link = Twitch.uri.game("Creative") + "/" + token.tag;
- return '' + utils.sanitize(token.text) + ' ';
- }
-
- else if ( token.type === "link" ) {
- var text = token.title || (token.isLong && '') || (token.isDeleted && '') || (warn_links && '') || token.text;
-
- if ( ! render_links && render_links !== undefined )
- return utils.sanitize(text);
-
- var href = token.link || token.text,
- cls = '';
-
- if ( token.isMailTo ) {
- // E-Mail Link
- cls = 'email-link';
- href = 'mailto:' + href;
-
- } else {
- // Web Link
- cls = 'chat-link';
-
- var info = this.get_link_info(href);
- if ( info && info.unsafe )
- cls += ' unsafe-link';
- }
-
- // Deleted Links
- var actual_href = href;
- if ( token.isDeleted ) {
- cls = 'deleted-link ' + cls;
- href = '#';
-
- } else if ( warn_links ) {
- cls = 'warn-link deleted-link ' + cls;
- href = '#';
- }
-
- //return `${utils.sanitize(text)} `;
- return '' + utils.sanitize(text) + ' ';
- }
-
- else if ( token.type === "bits" ) {
- var tier = render_bits && bits_service.ffz_get_tier(token.prefix, token.amount) || [null, null];
- if ( ! tier[1] )
- return 'cheer' + token.amount;
-
- var prefix = utils.quote_attr(token.prefix);
- return ' ';
- }
-
- else if ( token.type === 'bits-tag' ) {
- return '' + utils.sanitize(token.tag) + ' ';
- }
-
- else if ( token.type === "deleted" )
- return '××× ';
- //return `××× `;
-
- else if ( token.type === "mention" )
- return '' + utils.sanitize(token.user) + ' ';
- //return `${utils.sanitize(token.user)} `;
-
- else if ( token.deletedLink || token.hasOwnProperty('text') )
- return utils.sanitize(token.text);
-
- else if ( typeof token !== "string" )
- return '[unknown token] ';
- //return `[unknown token] `;
-
- return utils.sanitize(token);
-}
-
-
-FFZ.prototype.render_tokens = function(tokens, render_links, warn_links, render_bits) {
- return _.map(tokens, this.render_token.bind(this, render_links, warn_links, render_bits)).join("");
-}
-
-
-// ---------------------
-// Creative Tags
-// ---------------------
-
-FFZ.prototype.tokenize_ctags = function(tokens, tags_only) {
- "use strict";
-
- if ( typeof tokens === "string" )
- tokens = [tokens];
-
- var banned_tags = window.SiteOptions && SiteOptions.creative_banned_tags && SiteOptions.creative_banned_tags.split(',') || [],
- new_tokens = [];
-
- for(var i=0, l = tokens.length; i < l; i++) {
- var token = tokens[i];
- if ( ! token )
- continue;
-
- if ( typeof token !== "string" )
- if ( token.type === "text" )
- token = token.text;
- else {
- ! tags_only && new_tokens.push(token);
- continue;
- }
-
- var segments = token.split(' '),
- text = [], segment, tag;
-
- for(var x=0,y=segments.length; x < y; x++) {
- segment = segments[x];
- tag = segment.substr(1).toLowerCase();
- if ( segment.charAt(0) === '#' && banned_tags.indexOf(tag) === -1 ) {
- if ( text.length ) {
- ! tags_only && new_tokens.push({type: "text", text: text.join(' ') + ' '});
- text = [];
- }
-
- new_tokens.push({type: "tag", text: segment, tag: tag});
- text.push('');
- } else
- text.push(segment);
- }
-
- if ( ! tags_only && (text.length > 1 || (text.length === 1 && text[0] !== '')) )
- new_tokens.push({type: "text", text: text.join(' ')});
- }
-
- return new_tokens;
-}
-
-
-// ---------------------
-// Emoticon Processing
-// ---------------------
-
-FFZ.prototype.tokenize_users = function(tokens) {
- "use strict";
-
- if ( typeof tokens === "string" )
- tokens = [tokens];
-
- var new_tokens = [];
- for(var i=0, l=tokens.length; i < l; i++) {
- var token = tokens[i];
- if ( ! token )
- continue;
-
- if ( typeof token !== "string" )
- if ( token.type === "text" )
- token = token.text;
- else {
- new_tokens.push(token);
- continue;
- }
-
- var segments = token.split(/(@[a-z0-9][a-z0-9_]{3,24})/i);
- for(var x=0, y = segments.length; x < y; x += 2) {
- var text = segments[x] || '',
- match = segments[x+1] || '';
-
- if ( text.length )
- new_tokens.push({type: 'text', text: text});
-
- if ( match.length )
- new_tokens.push({type: 'user', text: match, user: match.substr(1)});
- }
- }
-
- return new_tokens;
-}
-
-FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
- "use strict";
-
- var sets = this.getEmotes(user, room),
- emotes = {},
- emote,
-
- new_tokens = [];
-
- if ( ! tokens || ! tokens.length || ! sets || ! sets.length )
- return tokens;
-
- // Build an object with all of our emotes.
- for(var i=0; i < sets.length; i++) {
- var emote_set = this.emote_sets[sets[i]];
- if ( emote_set && emote_set.emoticons )
- for(var emote_id in emote_set.emoticons) {
- emote = emote_set.emoticons[emote_id];
- if ( ! HOP.call(emotes, emote.name) )
- emotes[emote.name] = emote;
- }
- }
-
- if ( typeof tokens === "string" )
- tokens = [tokens];
-
- var last_token;
- for(var i=0, l=tokens.length; i < l; i++) {
- var token = tokens[i];
- if ( ! token )
- continue;
-
- if ( typeof token !== "string" )
- if ( token.type === "text" )
- token = token.text;
- else {
- if ( token.type === 'emoticon' ) {
- emote = emotes[token.altText];
- if ( emote && emote.replaces ) {
- token = _.extend({}, emote.token);
- token.modifiers = [];
- new_tokens.push(token);
- last_token = token;
-
- if ( do_report && room )
- this.add_usage(room, emote);
-
- continue;
- }
-
-
- if ( ! token.modifiers )
- token.modifiers = [];
- }
-
- new_tokens.push(token);
- last_token = token;
- continue;
- }
-
- // Split the token!
- var segments = token.split(/ +/),
- text = [], segment;
-
- for(var x=0,y=segments.length; x < y; x++) {
- segment = segments[x];
- if ( HOP.call(emotes, segment) ) {
- emote = emotes[segment];
-
- // Is this emote a modifier?
- if ( emote.modifier && last_token && last_token.modifiers && (!text.length || (text.length === 1 && text[0] === '')) ) {
- if ( last_token.modifiers.indexOf(emote.token) === -1 )
- last_token.modifiers.push(emote.token);
-
- if ( do_report && room )
- this.add_usage(room, emote);
-
- continue;
- }
-
- if ( text.length ) {
- // We have pending text. Join it together, with an extra space
- // on the end for good measure.
- var token = {type: "text", text: text.join(' ') + ' '};
- new_tokens.push(token);
- if ( token.text.trim().length )
- last_token = token;
- text = []
- }
-
- // Push this emote to the tokens.
- var token = _.extend({}, emote.token);
- token.modifiers = [];
-
- new_tokens.push(token);
- last_token = token;
-
- if ( do_report && room )
- this.add_usage(room, emote);
-
- // Finally, push an empty string to text so that this emote gets spaced.
- text.push('');
-
- } else
- text.push(segment);
- }
-
- // Add any left over text from this segment.
- if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
- new_tokens.push({type: "text", text: text.join(' ')});
- }
-
- return new_tokens;
-}
-
-
-// ---------------------
-// Emoji Processing
-// ---------------------
-
-FFZ.prototype.tokenize_emoji = function(tokens) {
- "use strict";
- if ( ! tokens || ! tokens.length || ! this.emoji_data )
- return tokens;
-
- if ( typeof tokens === "string" )
- tokens = [tokens];
-
- var new_tokens = [];
-
- for(var i=0, l=tokens.length; i < l; i++) {
- var token = tokens[i];
- if ( ! token )
- continue;
-
- if ( typeof token !== "string" )
- if ( token.type === "text" )
- token = token.text;
- else {
- new_tokens.push(token);
- continue;
- }
-
- var segments = token.split(constants.EMOJI_REGEX),
- text = null;
-
- while(segments.length) {
- text = (text || '') + segments.shift();
-
- if ( segments.length ) {
- var match = segments.shift(),
- eid = utils.emoji_to_codepoint(match),
- data = this.emoji_data[eid];
-
- if ( data ) {
- if ( text && text.length )
- new_tokens.push(text);
- new_tokens.push(_.extend({modifiers: []}, data.token));
- text = null;
- } else
- text = (text || '') + match;
- }
- }
-
- if ( text && text.length )
- new_tokens.push(text);
- }
-
- return new_tokens;
-}
-
-
-// ---------------------
-// Mention Parsing
-// ---------------------
-
-FFZ._regex_cache = {};
-
-FFZ._get_regex = function(word) {
- return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig");
-}
-
-FFZ._words_to_regex = function(list) {
- var regex = FFZ._regex_cache[list];
- if ( ! regex ) {
- var reg = "";
- for(var i=0; i < list.length; i++) {
- if ( ! list[i] )
- continue;
-
- reg += (reg ? "|" : "") + (list[i].substr(0,6) === "regex:" ? list[i].substr(6) : reg_escape(list[i]));
- }
-
- regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + constants.SEPARATORS + ")(" + reg + ")(?=$|" + constants.SEPARATORS + ")", "ig");
- }
-
- return regex;
-}
-
-
-FFZ.prototype.tokenize_mentions = function(tokens) {
- var mention_words = this.settings.keywords;
- if ( ! mention_words || ! mention_words.length )
- return tokens;
-
- if ( typeof tokens === "string" )
- tokens = [tokens];
-
- var regex = FFZ._words_to_regex(mention_words),
- new_tokens = [];
-
- for(var i=0; i < tokens.length; i++) {
- var token = tokens[i];
- if ( token.type === "text" )
- token = token.text;
-
- if ( ! _.isString(token) || ! token.match(regex) ) {
- new_tokens.push(token);
- continue;
- }
-
- token = token.replace(regex, function(all, prefix, match) {
- new_tokens.push(prefix);
- new_tokens.push({
- type: "mention",
- length: match.length,
- user: match,
- isOwnMessage: false,
- });
-
- return "";
- });
-
- if ( token )
- new_tokens.push(token);
- }
-
- return new_tokens;
-}
-
-
-// ---------------------
-// Handling Bad Stuff
-// ---------------------
-
-FFZ.prototype._deleted_link_click = function(e) {
- if ( ! this.classList.contains("deleted-link") )
- return true;
-
- // Stop from Navigating
- e.preventDefault();
-
- // Get the URL
- var link = this.getAttribute('data-url'),
- text = this.getAttribute('data-text') || link,
- f = FrankerFaceZ.get();
-
- // Delete Old Stuff
- this.classList.remove('deleted-link');
- this.classList.remove('warn-link');
-
- // Set up the Link
- this.href = link;
- this.target = "_blank";
- this.textContent = text;
-
- // Refresh tipsy.
- jQuery(this).trigger('mouseout').trigger('mouseover');
-}
-
-
-// ---------------------
-// History Loading
-// ---------------------
-
-/*FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete_links, tmiSession, per_line) {
- var i = history.length, was_cleared = false;
- purged = purged || {};
- bad_ids = bad_ids || {};
-
- while(i--) {
- var msg = history[i],
- msg_id = msg.tags && msg.tags.id,
- is_deleted = msg.ffz_deleted = purged[msg.from] || (msg_id && bad_ids[msg_id]) || false;
-
- if ( is_deleted && ! this.settings.prevent_clear )
- msg.deleted = true;
-
- if ( ! msg.room && room_id )
- msg.room = room_id;
-
- if ( typeof msg.date === "string" || typeof msg.date === "number" )
- msg.date = utils.parse_date(msg.date);
-
- if ( ! msg.color )
- msg.color = msg.tags && msg.tags.color ? msg.tags.color : tmiSession && msg.from ? tmiSession.getColor(msg.from) : "#755000";
-
- if ( ! msg.labels || ! msg.labels.length ) {
- var labels = msg.labels = [];
-
- if ( msg.room && msg.room === msg.from )
- labels.push("owner");
- else if ( msg.tags ) {
- var ut = msg.tags['user-type'];
- if ( ut === 'mod' || ut === 'staff' || ut === 'admin' || ut === 'global_mod' )
- labels.push(ut);
- }
-
- if ( msg.tags ) {
- if ( msg.tags.turbo )
- labels.push("turbo");
- if ( msg.tags.subscriber )
- labels.push("subscriber");
- }
- }
-
- if ( ! msg.style ) {
- if ( msg.from === "jtv" )
- msg.style = "admin";
- else if ( msg.from === "twitchnotify" )
- msg.style = "notification";
- }
-
- if ( msg.tags && typeof msg.tags.emotes === "string" )
- msg.tags.emotes = utils.uncompressEmotes(msg.tags.emotes);
-
- if ( msg.tags && typeof msg.tags.badges === "string" )
- msg.tags.badges = utils.uncompressBadges(msg.tags.badges);
-
- if ( ! msg.cachedTokens || ! msg.cachedTokens.length )
- this.tokenize_chat_line(msg, true, delete_links);
-
- // CLEARCHAT
- if ( msg.tags && msg.tags.target === '@@' )
- was_cleared = true;
-
- else if ( msg.tags && msg.tags.target ) {
- var ban_reason = msg.tags && msg.tags['ban-reason'],
- ban_id = ban_reason && constants.UUID_TEST.exec(ban_reason);
-
- if ( ban_id ) {
- bad_ids[ban_id[1]] = true;
- ban_reason = ban_reason.substr(0, ban_reason.length - ban_id[0].length);
- msg.tags['ban-reason'] = ban_reason ? ban_reason : undefined;
- } else
- purged[msg.tags.target] = true;
- }
-
- // Per-line
- if ( per_line && ! per_line(msg) )
- break;
- }
-
- return [history, purged, was_cleared];
-}*/
\ No newline at end of file
diff --git a/src/ui/about_page.js b/src/ui/about_page.js
deleted file mode 100644
index 8eb8451a..00000000
--- a/src/ui/about_page.js
+++ /dev/null
@@ -1,655 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require("../constants"),
- utils = require("../utils"),
- createElement = utils.createElement,
-
- BANNED_KEYS = ['user_ip'];
-
-
-// -------------------
-// Initialization
-// -------------------
-
-/*FFZ.prototype._has_news = false;
-FFZ.prototype._news_id = 0;
-
-FFZ.prototype.check_news = function(tries) {
- jQuery.ajax(constants.SERVER + "script/news.json", {cache: false, dataType: "json", context: this})
- .done(function(data) {
- FFZ.ws_commands.update_news.call(this, data.id);
- }).fail(function(data) {
- if ( data.status === 404 )
- return;
-
- tries = (tries || 0) + 1;
- if ( tries < 10 )
- setTimeout(this.check_news.bind(this, tries), Math.floor(Math.random()*5)*1000);
- });
-}
-
-
-FFZ.ws_commands.update_news = function(version) {
- var old_version = parseInt(localStorage.ffzLastNewsId || "0") || 0;
- if ( ! old_version || Number.isNaN(old_version) || old_version < 0 )
- old_version = 0;
-
- if ( version <= old_version ) {
- this._news_id = old_version;
- return;
- }
-
- this._has_news = true;
- this._news_id = version;
- this.update_ui_link();
-}*/
-
-
-// -------------------
-// About Page
-// -------------------
-
-FFZ.debugging_blocks = {
- navigator: {
- order: 1,
- title: 'Navigator',
- refresh: false,
- type: 'list',
-
- render: function() {
- var flash = _.find(navigator.plugins, function(x) { return x.name.indexOf('Flash') !== -1});
- return [
- ['User Agent', navigator.userAgent],
- ['Screen Size', screen.width + 'x' + screen.height + ' ' + screen.pixelDepth + 'bpp ' + window.devicePixelRatio + 'x DPI'],
- ['Flash Version', flash ? flash.description : 'not found ']
- ];
- }
- },
-
- version: {
- order: 2,
- title: "Version Breakdown",
- refresh: false,
- type: "list",
-
- render: function() {
- var output = [
- ['Ember', Ember.VERSION],
- ['Ember Data', window.DS && DS.VERSION || 'unknown '],
- ['GIT Version', EmberENV.GIT_VERSION],
- null,
- ['FrankerFaceZ', FFZ.version_info.toString()]
- ];
-
- if ( this.has_bttv )
- output.push(['BetterTTV', this.has_bttv_7 ? BetterTTV.version : (BetterTTV.info.version + 'r' + BetterTTV.info.release)]);
-
- if ( Object.keys(this._apis).length ) {
- output.push(null);
- for(var key in this._apis) {
- var api = this._apis[key];
- output.push(['Ext #' + api.id + '. ' + api.name, api.version || 'unknown ']);
- }
- }
-
- return output;
- }
- },
-
- socket: {
- order: 3,
- title: "WS Client Status",
- refresh: 5000,
- type: "list",
-
- render: function() {
- this.ws_ping(true);
-
- var last_ping = this._ws_last_ping;
- if ( typeof last_ping === "number" )
- last_ping = (Math.floor(last_ping * 1000) / 1000) + 'ms';
-
- var offset = this._ws_sock && this._ws_server_offset && (Math.floor(this._ws_server_offset) / 1000);
- if ( typeof offset === "number" )
- offset = (offset < 0 ? '-' : '') + utils.time_to_string(Math.abs(offset));
-
- return [
- ['Client ID', localStorage.ffzClientId || 'not set '],
- ['Socket Server', this._ws_sock && this._ws_sock.url || 'disconnected '],
- ['Authenticated', this._ws_open && this._ws_authenticated],
- ['Server Ping', last_ping || 'unknown '],
- ['Time Offset', offset || 'unknown ']
- ]
- }
- },
-
- socket_servers: {
- order: 3,
- title: 'WS Server Status',
- refresh: 5000,
- type: 'list',
-
- render: function() {
- var f = this;
- return new Promise(function(succeed, fail) {
- f.ws_send("get_server_status", null, function(s,data) {
- if ( ! s )
- return fail();
-
- f._ws_authenticated = data['authenticated'];
- var output = [];
- for(var key in data)
- if ( key !== 'authenticated' )
- output.push([key, data[key]]);
-
- succeed(output);
- })
- });
- }
- },
-
- logviewer: {
- order: 4,
- title: "Logviewer Status",
- refresh: true,
- type: "list",
-
- render: function() {
- var f = this,
- chat = utils.ember_lookup('controller:chat'),
- room_id = chat && chat.get('currentRoom.id'),
- ffz_room = room_id && this.rooms[room_id];
-
- return new Promise(function(succeed, fail) {
- var failed = false,
- fail_timer = setTimeout(function() {
- failed = true;
- succeed([['Authentication', 'unable to get token ']]);
- }, 500);
-
- f.lv_get_token().then(function(token) {
- if ( failed )
- return;
- else
- clearTimeout(fail_timer);
-
- var output = [
- ['Authentication', 'succeeded '],
- ['Token Expires', new Date(f._lv_token.expires * 1000).toLocaleString()],
- ['Socket Server', f._lv_ws_sock && f._lv_ws_sock.url || 'disconnected '],
- ['Socket Topics', f._lv_ws_topics && _.map(f._lv_ws_topics, function(x) { return '' + x + '
' }).join(', ') || 'no topics ']
- ];
-
- if ( ! ffz_room ) {
- output.push(['Current Room', 'none ']);
- return succeed(output);
- }
-
- output.push(['Current Room', room_id]);
-
- if ( ! ffz_room.logviewer_levels ) {
- output.push(['Logging Enabled', 'loading ']);
-
- utils.logviewer.get('channel/' + room_id, token)
- .then(utils.json).then(function(result) {
- f.log("[LV] Channel Info: " + room_id, result);
- ffz_room.logviewer_levels = result;
- });
-
- return succeed(output);
- }
-
- var data = ffz_room.logviewer_levels;
-
- if ( ! data.channel ) {
- output.push(['Logging Enabled', false]);
- return succeed(output);
- }
-
- var perms = [],
- ul = data.me.valid ? data.me.level : 0,
- chan = data.channel;
-
- ul >= chan.viewlogs && perms.push('view');
- ul >= chan.viewmodlogs && perms.push('view-mod');
- ul >= chan.viewcomments && perms.push('comment-view');
- ul >= chan.writecomments && perms.push('comment-write');
- ul >= chan.deletecomments && perms.push('comment-delete');
-
- output.push(['Logging Enabled', data.channel.active === 1]);
- output.push(['User Level', data.me.valid ? data.me.level : 'invalid ']);
- output.push(['User Permissions', perms.join(', ') || 'none ']);
-
- succeed(output);
-
- }).catch(function(err) {
- succeed([['Authentication', 'unable to get token ']]);
- });
- });
- }
- },
-
- twitch: {
- order: 5,
- title: "Twitch Configuration",
- refresh: false,
- type: "list",
-
- render: function() {
- var user = this.get_user(),
- output = [ ['Deploy Flavor', SiteOptions.deploy_flavor] ];
-
- if ( user && user.login ) {
- output.push(['Current User', user.login + ' [' + user.id + ']']);
- var us = [];
-
- user.is_staff && us.push('staff');
- user.is_admin && us.push('admin');
- user.is_partner && us.push('partner');
- user.is_broadcaster && us.push('broadcaster');
- user.has_premium && us.push('premium');
- user.has_turbo && us.push('turbo');
- user.account_verified && us.push('verified');
-
- output.push(['User State', us.join(', ') || 'none ']);
- } else
- output.push(['Current User', 'not logged in ']);
-
- if ( window.Twitch && Twitch.geo && Twitch.geo._result ) {
- var data = Twitch.geo._result;
- if ( data.geo )
- output.push(['Region', data.geo + (data.eu ? ' [EU]' : '')]);
-
- if ( data.received_language )
- output.push(['Received Language', data.received_language]);
- }
-
- return output;
- }
- },
-
- experiments: {
- order: 6,
- title: "Twitch Experiments",
- refresh: false,
- type: "list",
-
- render: function() {
- var exp_service = utils.ember_lookup('service:experiments'),
- output = [];
-
- if ( exp_service ) {
- for(var key in exp_service.values) {
- if ( ! exp_service.values.hasOwnProperty(key) )
- continue;
-
- output.push([key, exp_service.values[key]]);
- }
- }
-
- return output;
- }
- },
-
- memory: {
- order: 7,
- title: "Memory Statistics",
- refresh: true,
- type: "list",
-
- visible: function() { return window.performance && performance.memory },
-
- render: function() {
- var mem = performance.memory;
- return [
- ['jsHeapSizeLimit', utils.format_size(mem.jsHeapSizeLimit) + ' (' + mem.jsHeapSizeLimit + ')'],
- ['totalJSHeapSize', utils.format_size(mem.totalJSHeapSize) + ' (' + mem.totalJSHeapSize + ')'],
- ['usedJSHeapSize', utils.format_size(mem.usedJSHeapSize) + ' (' + mem.usedJSHeapSize + ')']
- ]
- }
- },
-
- player: {
- order: 8,
- title: "Player Statistics",
- refresh: true,
- type: "list",
-
- get_player: function() {
- if ( this._player && ! this._player.isDestroyed )
- return this._player.player;
- },
-
- visible: function() { return FFZ.debugging_blocks.player.get_player.call(this) },
-
- render: function() {
- var player = FFZ.debugging_blocks.player.get_player.call(this),
- data;
-
- try {
- data = player.getVideoInfo();
- } catch(err) {}
-
- if ( ! data )
- return [];
-
- try {
- data.backend = player.getBackend();
- data.version = player.getVersion();
- data.quality = player.getVariant();
- data.qualities = _.map(player.getQualities(), function(x) { return x.group });
- } catch(err) {}
-
- var sorted_keys = Object.keys(data).sort(),
- output = [];
-
- for(var i=0; i < sorted_keys.length; i++) {
- var key = sorted_keys[i];
- if ( BANNED_KEYS.indexOf(key) === -1 )
- output.push([key, data[key]]);
- }
-
- return output;
- }
- },
-
- settings: {
- order: 9,
- title: "Current Settings",
- refresh: false,
- type: "text",
-
- render: function() {
- var output = this._get_settings_object(true).settings;
- delete output.favorite_settings;
- delete output.mod_card_reasons;
- delete output.emote_menu_collapsed;
- delete output.favorite_emotes;
-
- return JSON.stringify(output, null, 2);
- }
- },
-
- logs: {
- order: 100,
- title: "Logs",
- refresh: false,
- type: "text",
-
- render: function() {
- return this._log_data.join("\n");
- }
- }
-}
-
-FFZ.prototype._sorted_blocks = function(blocks) {
- var segments = [];
- for(var key in blocks) {
- var info = blocks[key];
- if ( ! info )
- continue;
-
- var visible = info.visible || true;
- if ( typeof visible === "function" )
- visible = visible.call(this);
-
- if ( ! visible )
- continue;
-
- segments.push([info.order || 50, info]);
- }
-
- segments.sort(function(a,b) { return a[0] > b[0] });
- return segments;
-}
-
-FFZ.prototype.get_debugging_info = function() {
- var f = this;
- return new Promise(function(succeed, fail) {
- var output = [
- 'FrankerFaceZ - Debugging Information',
- (new Date).toISOString(), ''];
-
- var segments = f._sorted_blocks(FFZ.debugging_blocks),
- promises = [];
-
- for(var i=0; i < segments.length; i++) {
- var info = segments[i][1];
- promises.push(new Promise(function(info, s) {
- var result = info.render.call(f);
- if (!( result instanceof Promise ))
- result = Promise.resolve(result);
-
- result.then(function(data) {
- var el = utils.createElement('span'),
- out = [info.title, '----------------------------------------'];
- if ( info.type === 'list' )
- for(var x=0; x < data.length; x++) {
- if ( data[x] ) {
- el.innerHTML = data[x].join(': ');
- out.push(el.textContent);
- } else
- out.push('');
- }
- else if ( info.type === 'text' )
- out.push(data);
-
- s(out);
- }).catch(function(err) {
- s(['', info.title, 'Error: ' + err]);
- });
-
- }.bind(f, info)));
- }
-
- Promise.all(promises).then(function(result) {
- for(var i=0; i < result.length; i++) {
- output.push.apply(output, result[i]);
- output.push('');
- output.push('');
- }
-
- succeed(output.join('\n').trim());
- });
- });
-}
-
-
-var include_html = function(heading_text, filename, callback) {
- return function(view, container) {
- var heading = createElement('div', 'chat-menu-content center');
- heading.innerHTML = 'FrankerFaceZ ' + (heading_text ? '' + heading_text + '
' : '');
-
- jQuery.ajax(filename, {cache: false, context: this})
- .done(function(data) {
- container.appendChild(heading);
- container.innerHTML += data;
-
- jQuery('#ffz-old-news-button', container).on('click', function() {
- jQuery(this).remove();
- var onc = jQuery('#ffz-old-news', container);
- onc.load(constants.SERVER + "script/old_changes.html", function() {
- onc.css('display', 'block');
- });
- });
-
- typeof callback === "function" && callback(view, container);
-
- }).fail(function(data) {
- var content = createElement('div', 'chat-menu-content menu-side-padding');
- content.textContent = 'There was an error loading this page from the server.';
-
- container.appendChild(heading);
- container.appendChild(content);
- });
- }
- },
- render_news = include_html("news", constants.SERVER + "script/news.html");
-
-
-FFZ.menu_pages.about = {
- name: "About",
- icon: constants.HEART,
- sort_order: 100000,
-
- pages: {
- about: {
- name: "About",
- render: function(view, container, inner, menu) {
- var room = this.rooms[view.get('room.id')],
- has_emotes = false, f = this;
-
- if ( room && room.set ) {
- var set = this.emote_sets[room.set];
- if ( set && set.count > 0 )
- has_emotes = true;
- }
-
- // Heading
- var heading = createElement('div'),
- content = '';
-
- content += "FrankerFaceZ ";
- content += 'new ways to woof
';
-
- heading.className = 'chat-menu-content center';
- heading.innerHTML = content;
- container.appendChild(heading);
-
- var clicks = 0, head = heading.querySelector("h1");
- head && head.addEventListener("click", function() {
- head.style.cursor = "pointer";
- clicks++;
- if ( clicks >= 3 ) {
- clicks = 0;
- var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
- el && el.classList.toggle('ffz-flip');
- }
- setTimeout(function(){clicks=0;head.style.cursor=""},2000);
- });
-
-
- // Button Stuff
- var btn_container = createElement('div', 'chat-menu-content center'),
- more_buttons = createElement('div', 'chat-menu-content center'),
-
- ad_button = createElement('a', 'button primary', 'Advertise in Chat'),
- donate_button = createElement('a', 'button ffz-donate mg-l-1', 'Donate'),
- issue_button = createElement('a', 'button ffz-issues', 'Bugs & Issues'),
- idea_button = createElement('a', 'button ffz-ideas mg-l-1', 'Ideas'),
-
- message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from https://www.frankerfacez.com";
-
- // Advertising
- ad_button.addEventListener('click', this._add_emote.bind(this, view, message));
- btn_container.appendChild(ad_button);
-
- // Donate
- donate_button.href = "https://www.frankerfacez.com/donate";
- donate_button.target = "_blank";
- btn_container.appendChild(donate_button);
-
- // Issues
- issue_button.href = "https://github.com/FrankerFaceZ/FrankerFaceZ/labels/bug";
- issue_button.target = "_blank";
- more_buttons.appendChild(issue_button);
-
- // Ideas
- idea_button.href = "https://github.com/FrankerFaceZ/FrankerFaceZ/labels/enhancement";
- idea_button.target = "_blank";
- more_buttons.appendChild(idea_button);
-
-
- container.appendChild(btn_container);
- container.appendChild(more_buttons);
-
-
- // Credits
- var credits = createElement('div');
-
- content = '';
- content += 'Developers ';
- content += 'Dan Salvato ';
- content += 'Stendec ';
-
- content += 'Version ' + FFZ.version_info + ' Logs ';
-
- credits.className = 'chat-menu-content center';
- credits.innerHTML = content;
-
- // Make the Version clickable.
- credits.querySelector('#ffz-changelog').addEventListener('click',
- f._ui_change_subpage.bind(f, view, inner, menu, container, 'changelog'));
-
- // Make the Logs button functional.
- var getting_logs = false;
- credits.querySelector('#ffz-debug-logs').addEventListener('click', function() {
- if ( getting_logs )
- return;
-
- getting_logs = true;
-
- f.get_debugging_info().then(function(data) {
- f._pastebin(data).then(function(url) {
- getting_logs = false;
- prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url);
- }).catch(function() {
- getting_logs = false;
- alert("An error occured uploading your FrankerFaceZ logs.");
- });
- });
- });
-
- container.appendChild(credits);
- }
- },
-
- changelog: {
- name: "Changes",
- wide: true,
- render: include_html("change log", constants.SERVER + "script/changelog.html")
- },
-
- /*news: {
- name: "News",
- wide: true,
- render: function(view, container) {
- if ( this._has_news ) {
- this._has_news = false;
- localStorage.ffzLastNewsId = this._news_id;
- this.update_ui_link();
- }
-
- return render_news.call(this, view, container);
- }
- },*/
-
- credits: {
- name: "Credit",
- wide: true,
- render: include_html("credits", constants.SERVER + "script/credits.html")
- },
-
- /*status: {
- name: "Status",
- wide: true,
- render: include_html("server status", constants.SERVER + "script/status.html", function(view, container) {
-
- })
- },*/
-
- debugging: {
- name: "Debug",
- wide: true,
- render: function(view, container) {
- // Heading!
- container.appendChild(createElement('div', 'chat-menu-content center',
- 'FrankerFaceZ woofs for nerds
'));
-
- // Stuff
- utils.render_update_list(this,
- this._sorted_blocks(FFZ.debugging_blocks),
- container, 'chat-menu-content menu-side-padding');
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ui/channel_stats.js b/src/ui/channel_stats.js
deleted file mode 100644
index 31b146b5..00000000
--- a/src/ui/channel_stats.js
+++ /dev/null
@@ -1,409 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils'),
-
- metadata = FFZ.channel_metadata;
-
-
-// --------------
-// Channel Stats
-// --------------
-
-metadata.viewers = {
- refresh: 5000,
- host_order: 100,
-
- static_label: constants.LIVE,
- label: function(view, channel, is_hosting) {
- var viewers = channel.get('stream.viewers');
- return is_hosting && viewers && utils.number_commas(viewers);
- },
-
- tooltip: "Watching Now"
-};
-
-metadata.followers = {
- refresh: 5000,
- host_order: 199,
-
- static_label: constants.HEART,
- label: function(view, channel, is_hosting) {
- var followers = channel.get('followers.content.meta.total');
- return is_hosting && followers && utils.number_commas(followers);
- },
-
- tooltip: "Total Followers"
-}
-
-metadata.views = {
- refresh: 5000,
- host_order: 200,
-
- static_label: constants.EYE,
- label: function(view, channel, is_hosting) {
- var views = channel.get('views');
- return is_hosting && views && utils.number_commas(views);
- },
-
- tooltip: "Total Views"
-}
-
-metadata.uptime = {
- refresh: function(channel) { return this.settings.stream_uptime > 0; },
-
- setup: function(view, channel) {
- var online = channel.get('stream.createdAt'),
- now = Date.now() - (this._ws_server_offset || 0),
-
- uptime = online && Math.floor((now - online.getTime()) / 1000) || -1;
-
- return [online, uptime];
- },
-
- order: 2,
- host_order: 101,
-
- static_label: constants.CLOCK,
- label: function(online, uptime) {
- var setting = this.settings.stream_uptime;
- if ( uptime < 0 || ! setting )
- return null;
-
- return utils.time_to_string(uptime, false, false, false, setting === 1 || setting === 3);
- },
-
- tooltip: function(online) {
- return 'Stream Uptime (since ' + online.toLocaleString() + ') ';
- }
-};
-
-metadata.player_stats = {
- refresh: function() { return this.settings.player_stats !== 0 },
-
- setup: function(view, maybe_channel, is_hosting, channel) {
- var channel_id = channel.get('id'),
- player_cont = this._player,
- player = player_cont && player_cont.player,
- stats;
-
- try {
- stats = player.getVideoInfo();
- } catch(err) { }
-
- // Check for server offset.
- var delayed = undefined;
- if ( this._ws_open ) {
- var offset = this._ws_server_offset;
- if ( isNaN(offset) || ! isFinite(offset) )
- this.ws_ping();
- else
- delayed = offset > 5000;
- }
-
- var delay = stats && Math.round(stats.hls_latency_broadcaster / 10) / 100;
- return [stats, delay, delay > 180, player_cont, delayed];
- },
-
- order: 3,
- host_order: 102,
-
- static_label: constants.GRAPH,
- label: function(stats, delay, is_old, player_cont, delayed) {
- if ( ! this.settings.player_stats || ! delay )
- return null;
-
- delayed = delayed ? '(!) ' : '';
-
- if ( is_old )
- return delayed + utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'
- else
- return delayed + delay.toFixed(2) + 's';
- },
-
- color: function(stats, delay, is_old) {
- var setting = this.settings.player_stats;
- if ( setting === -1 || is_old )
- return '';
-
- else if ( delay > (setting * 2) )
- return '#fc3636';
-
- else if ( delay > setting )
- return '#fc7835';
- },
-
- click: function(event, button, stats, delay, is_old, player_cont) {
- player_cont.$('.js-stats-toggle').click();
- },
-
- tooltip: function(stats, delay, is_old, player_cont, delayed) {
- delayed = delayed ? 'Your local clock seems to be off by roughly ' + (Math.round(this._ws_server_offset / 10) / 100).toFixed(2) + ' seconds, and that could be making this inaccurate. ' : '';
-
- if ( ! stats || ! stats.hls_latency_broadcaster )
- return delayed + 'Stream Latency';
-
- var bitrate;
- if ( stats.playback_bytes_per_second )
- bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
- else
- bitrate = Math.round(stats.current_bitrate * 100) / 100;
-
- return delayed + (is_old ? 'Video Information ' +
- 'Broadcast ' + utils.time_to_string(Math.floor(delay), true) + ' Ago ' : 'Stream Latency ') +
- 'Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p' + stats.current_fps + ' ' +
- 'Playback Rate: ' + utils.number_commas(bitrate) + ' Kbps ' +
- 'Dropped Frames: ' + utils.number_commas(stats.dropped_frames || 0);
- }
-};
-
-metadata.chatters = {
- refresh: false,
-
- static_label: constants.ROOMS,
- label: function(view, channel) {
- var channel_id = channel.get('id'),
- room = this.rooms[channel_id];
-
- if ( ! room || ! this.settings.chatter_count )
- return null;
-
- return utils.number_commas((room.room.get('ffz_chatters') || []).length);
- },
-
- tooltip: 'Currently in Chat'
-};
-
-metadata.host = {
- refresh: false,
-
- setup: function(view, channel) {
- var channel_id = channel.get('id'),
- user = this.get_user(),
- room = user && this.rooms[user.login] && this.rooms[user.login].room,
- now_hosting = room && room.ffz_host_target,
- hosts_remaining = room && room.ffz_hosts_left;
-
- return [user, channel_id, now_hosting, hosts_remaining, view.get('ffz_host_updating'), view];
- },
-
- order: 98,
- host_order: 3,
-
- label: function(user, channel_id, hosting_id) {
- if ( ! this.settings.stream_host_button || ! user || user.login === channel_id )
- return null;
-
- return channel_id === hosting_id ? 'Unhost' : 'Host';
- },
-
- button: true,
- disabled: function(user, channel_id, hosting_id, hosts_remaining, updating, view) {
- return !!view.get('ffz_host_updating')
- },
-
- click: function(event, button, user, channel_id, hosting_id, hosts_remaining, updating, view) {
- view.set('ffz_host_updating', true);
- event.update_stat();
-
- var room = user && this.rooms[user.login] && this.rooms[user.login].room;
- if ( channel_id === hosting_id )
- room.send('/unhost', true);
- else
- room.send('/host ' + channel_id, true);
-
- return true;
- },
-
- tooltip: function(user, channel_id, hosting_id, hosts_remaining, updating) {
- var out;
- if ( updating )
- return 'Updating...';
-
- if ( hosting_id ) {
- var display_name = FFZ.get_capitalization(hosting_id);
- out = 'You are now hosting ' + this.format_display_name(display_name, hosting_id, true)[0] + '.';
- } else
- out = 'You are not hosting any channel.';
-
- return out + (hosts_remaining ? ' You have ' + hosts_remaining + ' host command' + utils.pluralize(hosts_remaining) + ' remaining this half hour.' : '');
- }
-};
-
-
-// ---------------
-// Rendering
-// ---------------
-
-FFZ.prototype.render_metadata = function(key, basic_info, metabar, timers, refresh_func, is_hosting) {
- var f = this,
- info = metadata[key],
- el = metabar.querySelector('.cn-metabar__ffz[data-key="' + key + '"]'),
-
- close = function() {
- if ( el )
- jQuery(el).remove();
- if ( f._popup && f._popup.id === 'ffz-metadata-popup' && f._popup.dataset.key === key )
- f.close_popup();
- };
-
- if ( timers[key] )
- clearTimeout(timers[key]);
-
- if ( ! info )
- return close();
-
- var data = info.setup ? info.setup.apply(this, basic_info) : basic_info;
- if ( ! (data instanceof Promise) )
- data = Promise.resolve(data);
-
- data.then(function(data) {
- el = metabar.querySelector('.cn-metabar__ffz[data-key="' + key + '"]');
- var refresh = typeof info.refresh === 'function' ? info.refresh.apply(f, data) : info.refresh;
- if ( refresh )
- timers[key] = setTimeout(function(){refresh_func(key)}, typeof refresh === "number" ? refresh : 1000);
-
- var je, stat, old_color,
- dynamic_tooltip = typeof info.tooltip === 'function',
- label = typeof info.label === 'function' ? info.label.apply(f, data) : info.label,
- color = (typeof info.color === 'function' ? info.color.apply(f, data) : info.color) || '';
-
- if ( ! label )
- return close();
-
- else if ( ! el ) {
- var btn,
- static_label = (typeof info.static_label === 'function' ? info.static_label.apply(f, data) : info.static_label) || '',
- lbl_start = static_label && static_label.substr(0,4);
-
- if ( lbl_start === ' (outer.right - rect.right));
-
- f._popup_kill = info.on_popup_close ? function() { info.on_popup_close.apply(f, data) } : null;
- f._popup_allow_parent = true;
- f._popup = balloon;
-
- el.appendChild(balloon);
- });
- }.bind(this, el))
-
- old_color = '';
- metabar.appendChild(el);
- el = btn;
-
- } else {
- stat = el.querySelector('span.ffz-label');
- old_color = el.getAttribute('data-color') || '';
- if ( dynamic_tooltip )
- je = jQuery(el);
- }
-
- if ( old_color !== color ) {
- el.dataset.color = color;
- el.style.color = color;
- var svg = el.querySelector('svg');
- if ( svg )
- svg.style.fill = color;
- }
-
- stat.innerHTML = label;
- if ( dynamic_tooltip && je.data('hover') )
- je.zipsy('hide').zipsy('show');
-
- if ( info.hasOwnProperty('disabled') )
- el.classList.toggle('disabled', typeof info.disabled === 'function' ? info.disabled.apply(f, data) : info.disabled);
-
- }).catch(function(err) {
- f.error("Error rendering metadata: " + key, err);
- close();
- });
-}
\ No newline at end of file
diff --git a/src/ui/dark.js b/src/ui/dark.js
deleted file mode 100644
index 0c6089ed..00000000
--- a/src/ui/dark.js
+++ /dev/null
@@ -1,266 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require("../constants"),
- utils = require("../utils");
- //styles = require("../styles");
-
-
-// ---------------------
-// Settings
-// ---------------------
-
-FFZ.basic_settings.dark_twitch = {
- type: "boolean",
- no_bttv: true,
-
- category: "General",
- name: "Dark Twitch",
- help: "Apply a dark background to channels and other related pages for easier viewing.",
-
- get: function() {
- return this.settings.dark_twitch;
- },
-
- set: function(val) {
- this.settings.set('dark_twitch', val);
- this.settings.set('dark_no_blue', val);
- }
-};
-
-FFZ.basic_settings.separated_chat = {
- type: "boolean",
- no_bttv: true,
-
- category: "Chat",
- name: "Separated Lines",
- help: "Use alternating rows and thin lines to visually separate chat messages for easier reading.",
-
- get: function() {
- return this.settings.chat_rows && this.settings.chat_separators !== '0';
- },
-
- set: function(val) {
- this.settings.set('chat_rows', val);
- this.settings.set('chat_separators', val ? '2' : '0');
- }
-};
-
-FFZ.basic_settings.minimalistic_chat = {
- type: "boolean",
-
- category: "Chat",
- name: "Minimalistic UI",
- help: "Hide all of chat except messages and the input box and reduce chat margins.",
-
- get: function() {
- return this.settings.minimal_chat === 3 && this.settings.chat_padding;
- },
-
- set: function(val) {
- this.settings.set('minimal_chat', val ? 3 : 0);
- this.settings.set('chat_padding', val);
- }
-};
-
-FFZ.basic_settings.high_contrast = {
- type: "boolean",
-
- category: "Chat",
- name: "High Contrast",
- help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.",
-
- get: function() {
- return this.settings.high_contrast_chat !== '222';
- },
-
- set: function(val) {
- this.settings.set('high_contrast_chat', val ? '111': '222');
- }
-};
-
-FFZ.basic_settings.keywords = {
- type: "button",
-
- category: "Chat",
- no_bttv: 6,
-
- name: "Highlight Keywords",
- help: "Set additional keywords that will be highlighted in chat.",
-
- method: function() {
- FFZ.settings_info.keywords.method.call(this, null, true);
- }
-};
-
-FFZ.basic_settings.banned_words = {
- type: "button",
-
- category: "Chat",
- no_bttv: 6,
-
- name: "Banned Keywords",
- help: "Set a list of words that will be removed from chat messages, locally.",
-
- method: function() {
- FFZ.settings_info.banned_words.method.call(this, null, true);
- }
-};
-
-
-
-FFZ.settings_info.twitch_chat_dark = {
- type: "boolean",
- value: false,
- visible: false
- };
-
-
-FFZ.settings_info.twitch_theme = {
- value: '',
- visible: false
-};
-
-
-FFZ.settings_info.dark_twitch = {
- type: "boolean",
- value: false,
-
- no_bttv: true,
- //visible: function() { return ! this.has_bttv },
-
- category: "Appearance",
- name: "Dark Twitch",
- help: "Apply a dark background to channels and other related pages for easier viewing.",
-
- on_update: function(val) {
- var cb = document.querySelector('input.ffz-setting-dark-twitch');
- if ( cb )
- cb.checked = val;
-
- if ( this.has_bttv )
- return;
-
- var RH = this._roomv && this._roomv.get('ffz_recent_el');
- if ( RH )
- RH.classList.toggle('dark', val);
-
- (this.is_clips ? document.querySelector('html') : document.body).classList.toggle("ffz-dark", val);
-
- var Settings = utils.ember_settings(),
- ThemeManager = utils.ember_lookup('service:theme-manager');
-
- if ( val ) {
- this._load_dark_css();
- if ( ThemeManager ) {
- this.settings.set('twitch_theme', ThemeManager.get('themes.activeTheme'));
- ThemeManager.set('themes.activeTheme', 'theme--dark');
- }
- if ( Settings ) {
- this.settings.set('twitch_chat_dark', Settings.get('darkMode'));
- Settings.set('darkMode', true);
- }
- } else {
- if ( ThemeManager )
- ThemeManager.set('themes.activeTheme', this.settings.twitch_theme);
- if ( Settings )
- Settings.set('darkMode', this.settings.twitch_chat_dark);
- }
-
- // Try coloring chat replay
- window.jQuery && jQuery('.chatReplay').toggleClass('dark', val || false);
- //jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark);
- }
- };
-
-
-FFZ.settings_info.dark_no_blue = {
- type: "boolean",
- value: false,
-
- //no_bttv: true,
-
- category: "Appearance",
- name: "Gray Chat (no blue)",
- help: "Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.",
-
- on_update: function(val) {
- document.body.classList.toggle("ffz-no-blue", val);
- }
- };
-
-
-FFZ.settings_info.hide_recent_past_broadcast = {
- type: "boolean",
- value: false,
-
- //no_bttv: true,
- no_mobile: true,
-
- category: "Channel Metadata",
- name: "Hide \"Watch Last Broadcast\"",
- help: "Hide the \"Watch Last Broadcast\" banner at the top of offline Twitch channels.",
-
- on_update: function(val) {
- document.body.classList.toggle("ffz-hide-recent-past-broadcast", val);
- }
- };
-
-
-// ---------------------
-// Initialization
-// ---------------------
-
-FFZ.prototype.setup_dark = function() {
- document.body.classList.toggle("ffz-hide-recent-past-broadcast", this.settings.hide_recent_past_broadcast);
- document.body.classList.toggle("ffz-no-blue", this.settings.dark_no_blue);
-
- if ( this.has_bttv )
- return;
-
- (this.is_clips ? document.querySelector('html') : document.body).classList.toggle('ffz-dark', this.settings.dark_twitch);
-
- if ( ! this.settings.dark_twitch )
- return;
-
- var Settings = utils.ember_settings(),
- ThemeManager = utils.ember_lookup('service:theme-manager');
-
- if ( ThemeManager ) {
- ThemeManager.set('themes.activeTheme', 'theme--dark');
- } else if ( Settings )
- Settings.set('darkMode', true);
-
- this._load_dark_css();
-}
-
-
-FFZ.prototype._load_dark_css = function() {
- if ( this._dark_style )
- return;
-
- this.log("Injecting FrankerFaceZ Dark Twitch CSS.");
-
- var s = this._dark_style = document.createElement('link');
-
- s.id = "ffz-dark-css";
- s.setAttribute('rel', 'stylesheet');
- s.setAttribute('href', constants.SERVER + "script/dark" + (this.is_clips ? '-clips' : '') + (constants.DEBUG ? "" : ".min") + ".css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
- document.head.appendChild(s);
-}
-
-
-FFZ.prototype.add_clips_darken_button = function() {
- if ( this.embed_in_clips )
- return;
-
- this.log("Adding Clips Darken button.");
-
- var f = this,
- btn = utils.createElement('a', 'button button--hollow ffz-darken-clips', f.settings.dark_twitch ? 'Light' : 'Dark');
-
- btn.addEventListener('click', function() {
- f.settings.set('dark_twitch', ! f.settings.dark_twitch);
- btn.textContent = f.settings.dark_twitch ? 'Light' : 'Dark';
- });
-
- document.body.appendChild(btn);
-}
\ No newline at end of file
diff --git a/src/ui/dash_feed.js b/src/ui/dash_feed.js
deleted file mode 100644
index 7cbcdd25..00000000
--- a/src/ui/dash_feed.js
+++ /dev/null
@@ -1,197 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
-
- createElement = utils.createElement;
-
-
-// -------------------
-// Settings
-// -------------------
-
-FFZ.settings_info.dashboard_feed = {
- type: "boolean",
- value: true,
-
- no_mobile: true,
- //no_bttv: true,
-
- category: "Dashboard",
- name: "Channel Feed (Requires Refresh) ",
- help: "Add a way to post to your channel feed directly to the dashboard!"
-}
-
-
-// -------------------
-// Initialization
-// -------------------
-
-FFZ.prototype.setup_dash_feed = function() {
- var f = this,
- user = this.get_user(),
- match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined,
- id = this.is_dashboard && match && match[1];
-
- if ( /*this.has_bttv ||*/ ! this.settings.dashboard_feed || ! user || ! id || id !== user.login )
- return;
-
- utils.api.get("feed/" + id + "/posts", {limit: 1}, {version: 3}).done(function(data) {
- // If this works, then the feed is enabled. Show the UI.
- /*if ( ! f.has_bttv )*/
- f.build_dash_feed();
- })
-}
-
-
-FFZ.prototype._remove_dash_feed = function() {
- var tabs = document.querySelector('#ffz-feed-tabs'),
- parent = tabs && tabs.parentElement,
- tab_status = document.querySelector('div.dash-broadcast-contain');
-
- if ( ! tabs || ! parent )
- return;
-
- if ( tab_status && tabs.contains(tab_status) ) {
- tabs.removeChild(tab_status);
- parent.insertBefore(tab_status, tabs);
- }
-
- parent.removeChild(tabs);
-}
-
-
-FFZ.prototype.build_dash_feed = function() {
- var f = this,
- user = this.get_user(),
-
- tabs = createElement('div'),
- tab_bar = createElement('ul', 'tabs'),
-
- nav_status = createElement('li', 'tab', 'Broadcast Status '),
- nav_feed = createElement('li', 'tab', 'Channel Feed '),
-
- tab_status = document.querySelector('div.dash-broadcast-contain'),
- tab_feed = createElement('div', 'dash-broadcast-contain dash-ffz-feed-contain hidden'),
-
- txt_input = createElement('textarea', 'ffz-feed-entry'),
- char_count = createElement('span', 'char-count', '0');
- align_btn = createElement('div', 'ffz-feed-button clearfix'),
- chk_share = createElement('span', null, ' Share to Twitter'),
- checkbox = chk_share.querySelector('input'),
- btn_submit = createElement('button', 'button primary', 'Post '),
-
- column = tab_status && tab_status.parentElement,
- placeholder = 'Post an update to your channel...';
-
- if ( ! tab_status || ! column || ! user )
- return;
-
- tab_feed.appendChild(txt_input);
- tab_feed.appendChild(align_btn);
-
- if ( user.twitter_connected )
- align_btn.appendChild(chk_share);
- else {
- var tnc = createElement('span', null, '(Twitter Not Connected) ');
- tnc.title = 'Click to Refresh';
- tnc.style.cursor = 'pointer';
- tnc.addEventListener('click', function() {
- user = f.get_user();
- if ( user.twitter_connected ) {
- jQuery(tnc).trigger('mouseout');
- align_btn.removeChild(tnc);
- align_btn.insertBefore(chk_share, align_btn.firstChild);
- }
- });
- jQuery(tnc).zipsy();
- align_btn.appendChild(tnc);
- }
-
- char_count.title = 'Roughly the first 115 characters of a post will appear in a Tweet.';
- jQuery(char_count).zipsy();
-
- align_btn.appendChild(btn_submit);
- align_btn.appendChild(char_count);
-
- txt_input.id = 'vod_status';
- txt_input.placeholder = placeholder;
-
- var updater = function() {
- var share = user.twitter_connected && checkbox && checkbox.checked || false,
- len = txt_input.value.length;
-
- char_count.innerHTML = share ? utils.number_commas(115 - len) + '+ ' : utils.number_commas(len);
- char_count.classList.toggle('over-limit', share && 115-len < 0 || false);
-
- if ( len === 0 )
- txt_input.placeholder = placeholder;
- }
-
- checkbox.addEventListener('change', updater);
- txt_input.addEventListener('input', updater);
-
- btn_submit.addEventListener('click', function() {
- var match = f.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined,
- id = f.is_dashboard && match && match[1];
-
- if ( ! id || this.disabled )
- return;
-
- var share = user.twitter_connected && checkbox && checkbox.checked || false,
- body = txt_input.value;
-
- txt_input.disabled = true;
- btn_submit.disabled = true;
-
- utils.api.post("feed/" + id + "/posts?share=" + JSON.stringify(share), {content: body, share: share}).done(function(data) {
- txt_input.disabled = false;
- btn_submit.disabled = false;
-
- txt_input.value = '';
-
- var tweeted = data.tweet ? " The update was tweeted out." : (share ? " There was a problem tweeting the update." : "");
- txt_input.placeholder = 'The update was posted successfully.' + tweeted + ' Post another update to your channel...';
- f.log("Channel Feed Posting Succeeded", data);
-
- }).fail(function(data) {
- txt_input.disabled = false;
- btn_submit.disabled = false;
-
- data = data ? data.data || data.responseJSON || undefined : undefined;
- f.log("Channel Feed Posting Failed", data);
- alert("An error occured posting to your channel feed:\n\n" + (data && data.message || "Unknown Error"));
-
- });
- });
-
- var switch_tab = function(e) {
- var to = this.getAttribute('data-nav');
- jQuery('.selected', tab_bar).removeClass('selected');
- this.classList.add('selected');
-
- jQuery('.active-tab', tabs).removeClass('active-tab').addClass('hidden');
- jQuery('div[data-tab="' + to + '"]', tabs).addClass('active-tab').removeClass('hidden');
- }
-
- tabs.id = 'ffz-feed-tabs';
-
- nav_status.setAttribute('data-nav', 'status');
- nav_feed.setAttribute('data-nav', 'feed');
-
- nav_status.addEventListener('click', switch_tab);
- nav_feed.addEventListener('click', switch_tab);
-
- tab_status.setAttribute('data-tab', 'status');
- tab_feed.setAttribute('data-tab', 'feed');
-
- column.removeChild(tab_status);
-
- tab_bar.appendChild(nav_status);
- tab_bar.appendChild(nav_feed);
-
- tabs.appendChild(tab_bar);
- tabs.appendChild(tab_status);
- tabs.appendChild(tab_feed);
-
- column.insertBefore(tabs, column.firstChild);
- switch_tab.call(nav_status);
-}
\ No newline at end of file
diff --git a/src/ui/dash_stats.js b/src/ui/dash_stats.js
deleted file mode 100644
index d9809fc1..00000000
--- a/src/ui/dash_stats.js
+++ /dev/null
@@ -1,307 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants'),
-
- update_viewer_count = function(text) {
- var vc = jQuery("#channel_viewer_count");
- vc.text() === 'Hidden' || vc.text(text);
- };
-
-
-// -------------------
-// Settings
-// -------------------
-
-FFZ.settings_info.dashboard_graph = {
- type: "boolean",
- value: true,
-
- no_mobile: true,
- no_bttv: true,
-
- category: "Dashboard",
- name: "Statistics Graph (Requires Refresh) ",
- help: "Display a graph of your viewers, followers, and chat activity over time."
-}
-
-
-// -------------------
-// Collecting Chat
-// -------------------
-
-FFZ.msg_commands.chat_message = function(data) {
- if ( ! this.dashboard_channel || data.room !== this.dashboard_channel )
- return;
-
- this._stat_chat_lines++;
- if ( this._stat_chatters.indexOf(data.from) === -1 )
- this._stat_chatters.push(data.from);
-}
-
-
-FFZ.msg_commands.chatter_count = function(data) {
- if ( ! this.dashboard_channel || data.room !== this.dashboard_channel )
- return;
-
- var el = document.querySelector('#ffz-chatter-display span');
- if ( ! this.settings.chatter_count || this.has_bttv || ! this.is_dashboard || ! data.chatters ) {
- if ( el )
- jQuery(el).parent().remove();
- return;
- }
-
- if ( ! el ) {
- var cont = document.querySelector('#stats');
- if ( ! cont )
- return;
-
- var stat = utils.createElement('span', 'ffz stat', constants.ROOMS + ' ');
- stat.id = 'ffz-chatter-display';
- stat.title = 'Currently in Chat';
-
- el = utils.createElement('span');
- stat.appendChild(el);
-
- cont.appendChild(stat);
- jQuery(stat).zipsy({gravity: 's'});
- }
-
- el.textContent = utils.number_commas(data.chatters);
-}
-
-
-// -------------------
-// Initialization
-// -------------------
-
-FFZ.prototype.setup_dash_stats = function() {
- this._stat_chat_lines = 0;
- this._stat_chatters = [];
- this._stat_last_game = null;
- this._stat_last_status = null;
-
- this._update_dash_stats_timer = setTimeout(this.update_dash_stats.bind(this), 0);
-
- var f = this,
- stats = document.querySelector('#stats');
-
- if ( this.has_bttv || ! stats || ! window.Highcharts || ! this.settings.dashboard_graph )
- return;
-
- f.log("Adding dashboard statistics chart.");
-
- // Build a chart, under stats.
- var container = document.createElement('div');
- container.id = "chart_container";
- container.className = 'ffz-stat-chart';
- stats.parentElement.insertBefore(container, stats.nextSibling);
-
- Highcharts.setOptions({global: {useUTC: false}});
-
- // Load chart visibility
- var vis = {};
- if ( localStorage.ffz_dash_chart_visibility )
- vis = JSON.parse(localStorage.ffz_dash_chart_visibility);
-
- var date_format = this.settings.twenty_four_timestamps ? "%H:%M" : "%l:%M",
- chart = this._dash_chart = new Highcharts.Chart({
- chart: {
- type: 'line',
- zoomType: "x",
- animation: false,
- renderTo: container,
- height: 200
- },
-
- title: { text: null },
- credits: { enabled: false },
- exporting: { enabled: false },
- legend: {
- backgroundColor: "#fff"
- },
-
- xAxis: {
- type: 'datetime',
- tickPixelInterval: 150,
- dateTimeLabelFormats: {
- millisecond: date_format,
- second: date_format,
- minute: date_format
- }
- },
-
- tooltip: {
- formatter: function() {
- if ( this.point )
- this.points = [this.point];
-
- var s = [],
- key = this.points[0].key || this.points[0].x;
-
- if ( key ) {
- if ( typeof key === "number" )
- key = Highcharts.dateFormat((f.settings.twenty_four_timestamps ? "%H:%M" : "%l:%M %P"), key);
-
- s.push('' + key + ' ');
- }
-
- for(var i=0; i < this.points.length; i++) {
- var point = this.points[i],
- series = point.series,
- to = series.tooltipOptions,
- y = point.text || point.y;
-
- if ( ! to || ! to.enabled || y === undefined || y === null )
- continue;
-
- if ( typeof y === "number" )
- y = utils.number_commas(y);
-
- s.push('' + series.name + ' : ' + y + ' ');
- }
-
- return s.join(" ");
- },
- crosshairs: true,
- shared: true
- },
-
- yAxis: [
- {title: { text: null }, min: 0},
- {title: { text: null }, min: 0},
- {title: { text: null }, min: 0, opposite: true},
- {title: { text: null }, min: 0, opposite: true}
- ],
-
- series: [
- {
- type: 'flags',
- name: 'Status',
- showInLegend: false,
- shape: 'squarepin',
- data: [],
- zIndex: 5
- },
- {name: "Viewers", data: [], zIndex: 4, visible: vis.hasOwnProperty('viewers')?vis.viewers:true},
- {name: "Followers", data: [], yAxis: 1, zIndex: 3, visible: vis.hasOwnProperty('followers')?vis.followers:true},
- {name: "Subscribers", data: [], zIndex: 3, showInLegend: false, visible: vis.hasOwnProperty('subscribers')?vis.subscribers:true},
- {name: "Chat Lines", type: 'area', data: [], yAxis: 2, visible: vis.hasOwnProperty('chat_lines')?vis.chat_lines:false, zIndex: 1},
- {name: "Chatters", data: [], yAxis: 3, visible: vis.hasOwnProperty('chatters')?vis.chatters:false, zIndex: 2}
- ]
- });
-}
-
-
-FFZ.prototype._dash_chart_chatters = function(force) {
- var now = utils.last_minute(),
- series = this._dash_chart.series[4],
- len = series.data.length,
- last_point = len > 0 && series.data[len-1],
- rendered = false;
-
- if ( ! force && ! this._stat_chat_lines && len > 0 && last_point.y === 0 ) {
- series.addPoint({x: now, y: null}, false);
- this._dash_chart.series[5].addPoint({x: now, y: null}, false);
- rendered = true;
-
- } else if ( force || this._stat_chat_lines || len > 0 && last_point && last_point.y !== null ) {
- if ( this._stat_chat_lines !== 0 && last_point && last_point.y === null ) {
- series.addPoint({x:now-60000, y: 0}, false);
- this._dash_chart.series[5].addPoint({x:now-60000, y: 0}, false);
- }
-
- series.addPoint({x: now, y: this._stat_chat_lines || 0}, false);
- this._dash_chart.series[5].addPoint({x: now, y: this._stat_chatters.length || 0}, false);
- rendered = true;
- }
-
- this._stat_chat_lines = 0;
- this._stat_chatters = [];
- return rendered;
-}
-
-
-FFZ.prototype._remove_dash_chart = function() {
- if ( ! this._dash_chart )
- return;
-
- this.log("Removing dashboard statistics chart.");
-
- this._dash_chart.destroy();
- this._dash_chart = null;
- jQuery("#chart_container.ffz-stat-chart").remove();
-}
-
-
-FFZ.prototype.update_dash_stats = function() {
- var f = this,
- id = this.dashboard_channel;
-
- if ( this.has_bttv || ! id )
- return this._remove_dash_chart();
-
- this._update_dash_stats_timer = setTimeout(this.update_dash_stats.bind(this), 60000);
-
- if ( f._dash_chart ) {
- var series = f._dash_chart.series;
- localStorage.ffz_dash_chart_visibility = JSON.stringify({
- viewers: series[1].visble,
- followers: series[2].visible,
- subscribers: series[3].visible,
- chat_lines: series[4].visible,
- chatters: series[5].visible
- });
- }
-
- utils.api.get("streams/" + id , {}, {version: 3})
- .done(function(data) {
- var viewers = null,
- followers = null,
- game = null,
- status = null;
-
- if ( ! data || ! data.stream )
- !f.has_bttv && update_viewer_count("Offline");
-
- else {
- !f.has_bttv && update_viewer_count(utils.number_commas(data.stream.viewers));
- viewers = data.stream.viewers;
-
- var chan = data.stream.channel;
- if ( chan ) {
- followers = chan.hasOwnProperty('followers') ? chan.followers || 0 : null;
- game = chan.game || "Not Playing";
- status = chan.status || "Untitled Broadcast";
-
- if ( chan.views )
- jQuery("#views_count span").text(utils.number_commas(chan.views));
- if ( followers )
- jQuery("#followers_count span").text(utils.number_commas(followers));
- }
- }
-
- if ( f._dash_chart ) {
- var now = utils.last_minute(),
- force_draw;
-
- // If the game or status changed, we need to insert a pin.
- if ( f._stat_last_game !== game || f._stat_last_status !== status ) {
- f._dash_chart.series[0].addPoint({x: now, title: game, text: status}, false);
- f._stat_last_game = game;
- f._stat_last_status = status;
- }
-
- force_draw = utils.maybe_chart(f._dash_chart.series[1], {x: now, y: viewers}, false);
- force_draw = f._dash_chart_chatters(force_draw) || force_draw;
-
- utils.maybe_chart(f._dash_chart.series[2], {x: now, y: followers}, false, force_draw);
-
- f._dash_chart.redraw();
- }
- }).fail(function() {
- if ( f._dash_chart ) {
- f._dash_chart_chatters();
- f._dash_chart.redraw();
- }
- });
-}
\ No newline at end of file
diff --git a/src/ui/following-count.js b/src/ui/following-count.js
deleted file mode 100644
index c1b7cab1..00000000
--- a/src/ui/following-count.js
+++ /dev/null
@@ -1,350 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants'),
-
- FOLLOWING_CONTAINERS = [
- ['.warp__item a[data-href="following"]'],
- ['#header_actions #header_following'],
- ['.top-nav__nav-link[data-tt_content="directory_following"]', '.top-nav']
- ],
-
- WIDE_TIP = function(f, el) {
- return (f.settings.following_count && (
- (el.classList.contains('top-nav__nav-link') && el.dataset['tt_content'] === 'directory_following') ||
- el.id === 'header_following' ||
- el.getAttribute('data-href') === 'following' ||
- el.parentElement.getAttribute('data-name') === 'following'
-
- )) ? 'ffz-wide-tip' : '';
- };
-
-
-FFZ.settings_info.following_count = {
- type: "boolean",
- value: true,
-
- no_mobile: true,
-
- category: "Sidebar",
- name: "Following Data",
- help: "Display the number of live channels you're following on the sidebar, and list the channels in a tooltip.",
-
- on_update: function(val) {
- this._schedule_following_count();
-
- var Stream = utils.ember_resolve('model:deprecated-stream'),
- Live = Stream && Stream.find("live");
-
- if ( Live ) {
- var total = Live.get('total') || 0;
- this._draw_following_count(total);
- this._draw_following_channels(Live.get('content'), total);;
- } else {
- this._update_following_count();
- this._draw_following_channels();
- }
- }
- };
-
-// ---------------
-// Initialization
-// ---------------
-
-FFZ.prototype.setup_following_count = function(has_ember) {
- // Start it updating.
- if ( this.settings.following_count )
- this._schedule_following_count();
-
- // Tooltips~!
- this._install_following_tooltips();
- setTimeout(this._install_following_tooltips.bind(this), 2000);
-
- // If we don't have Ember, no point in trying this stuff.
- if ( this.is_dashboard || ! has_ember )
- return this._following_get_me();
-
- this.log("Connecting to Live Streams model.");
- var Stream = utils.ember_resolve('model:deprecated-stream');
- if ( ! Stream )
- return this.log("Unable to find Stream model.");
-
- var Live = Stream.find("live"),
- f = this;
-
- if ( ! Live )
- return this.log("Unable to find Live Streams collection.");
-
- Live.addObserver('total', function() { f._draw_following_count(this.get('total')); });
- Live.addObserver('content.length', function() { f._draw_following_channels(this.get('content'), this.get('total')); })
-
- Live.load();
-
- /*var Host = utils.ember_resolve('model:hist'),
- HostLive = Host && Host.find("following");
-
- if ( HostLive )
- HostLive.load();*/
-
- var init = function() {
- var total = Live.get('total'),
- streams = Live.get('content');
- if ( typeof total === "number" ) {
- f._draw_following_count(total);
- if ( streams && streams.length )
- f._draw_following_channels(streams, total);
- }
- }
-
- init()
- setTimeout(init, 2000);
-}
-
-
-FFZ.prototype._following_get_me = function(tries) {
- // get_user doesn't properly return an oauth token any longer, so we need to get me manually.
- if ( ! window.Twitch )
- // Wait around till the API shows up.
- return setTimeout(this._following_get_me.bind(this, tries), Math.floor(2000*Math.random()) + 500);
-
- var f = this;
- utils.api.get("/api/me").done(function(data) {
- f.log("Fetched User Data -- " + (data.name || data.login));
- f.__user = data;
- f._update_following_count();
-
- }).fail(function() {
- tries = (tries||0) + 1;
- if ( tries < 5 )
- return setTimeout(f._following_get_me.bind(f, tries), Math.floor(2000*Math.random()) + 500);
- f.log("Failed to get proper user object.");
- });
-}
-
-
-FFZ.prototype._schedule_following_count = function() {
- if ( ! this.settings.following_count ) {
- if ( this._following_count_timer ) {
- clearTimeout(this._following_count_timer);
- this._following_count_timer = undefined;
- }
- return;
- }
-
- if ( ! this._following_count_timer )
- this._following_count_timer = setTimeout(this._update_following_count.bind(this), 55000 + (10000*Math.random()));
-}
-
-
-FFZ.prototype._update_following_count = function() {
- if ( ! this.settings.following_count ) {
- if ( this._following_count_timer ) {
- clearTimeout(this._following_count_timer);
- this._following_count_timer = undefined;
- }
- return;
- }
-
- this._following_count_timer = setTimeout(this._update_following_count.bind(this), 55000 + (10000*Math.random()));
-
- var Stream = utils.ember_resolve('model:deprecated-stream'),
- Live = Stream && Stream.find("live"),
-
- /*Host = utils.ember_resolve('model:host'),
- HostLive = Host && Host.find("following"),*/
-
- current_path = document.body.getAttribute('data-current-path') || '',
- is_directory = current_path === 'directory.following.channels',
- f = this;
-
- /*if ( ! this.is_dashboard && HostLive && current_path.indexOf('directory.following') !== -1 )
- HostLive.load();*/
-
- if ( ! this.is_dashboard && ! is_directory && Live )
- Live.load();
- else {
- var u = this.get_user();
-
- utils.api.get("streams/followed", {limit:20, offset:0}, {version:3}, u && u.chat_oauth_token)
- .done(function(data) {
- f._draw_following_count(data._total);
- f._draw_following_channels(data.streams, data._total);
- }).fail(function() {
- f._draw_following_count();
- f._draw_following_channels();
- })
- }
-}
-
-
-FFZ.prototype._build_following_tooltip = function(el) {
- var is_top_nav = el.classList.contains('top-nav__nav-link') && el.dataset['tt_content'] === 'directory_following';
- if ( ! is_top_nav && el.id !== 'header_following' && el.getAttribute('data-href') !== 'following' && el.parentElement.getAttribute('data-name') !== 'following' )
- return el.getAttribute('original-title');
-
- if ( ! this.settings.following_count )
- return is_top_nav ? '' : 'Following';
-
- var tooltip = (this.has_bttv ? 'FrankerFaceZ ' : '') + 'Following',
- bb = el.getBoundingClientRect(),
- height = document.body.clientHeight - (bb.bottom + 50),
- max_lines = Math.max(Math.floor(height / 40) - 1, 2),
-
- /*Host = utils.ember_resolve('model:host'),
- HostLive = Host && Host.find("following"),*/
-
- streams = this._tooltip_streams,
- total = this._tooltip_total || (streams && streams.length) || 0,
- c = 0,
- filtered = 0;
-
- if ( streams && streams.length ) {
- for(var i=0, l = streams.length; i < l; i++) {
- var stream = streams[i];
- if ( ! stream || ! stream.channel )
- continue;
-
- if ( stream.game && this.settings.banned_games.indexOf(stream.game.toLowerCase()) !== -1 ) {
- filtered++;
- continue;
- }
-
- c += 1;
- if ( c > max_lines ) {
- tooltip += 'And ' + utils.number_commas(total - max_lines) + ' more' + (filtered ? ' (' + filtered + ' hidden)' : '') + '... ';
- filtered = 0;
- break;
- }
-
- var up_since = this.settings.stream_uptime && stream.created_at && utils.parse_date(stream.created_at),
- now = Date.now() - (this._ws_server_offset || 0),
- uptime = up_since && (Math.floor((now - up_since.getTime()) / 60000) * 60) || 0,
- channel_name = stream.channel.name,
- tags = stream.channel.game === 'Creative' && this.tokenize_ctags(stream.channel.status, true);
-
- tooltip += (c === 1 ? ' ' : '') +
- (uptime > 0 ? '' + constants.CLOCK + ' ' + utils.duration_string(uptime) + ' ' : '') +
- '' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + ' ' +
- '' + utils.sanitize(stream.channel.display_name || stream.channel.name) + ' ' +
- '' +
- (stream.stream_type === 'watch_party' ? 'Vodcast ' : '') +
- (stream.channel.game === 'Creative' ? 'Being Creative' : (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing')) + (tags ? ' | ' + _.pluck(tags, "text").join(" ") : '') + ' ';
- }
-
- if ( filtered )
- tooltip += '(' + filtered + ' hidden)';
-
- } else {
- c++; // is a terrible programming language
- tooltip += " No one you're following is online.";
- }
-
- // If we have hosts, and room, try displaying some hosts.
- /*if ( HostLive && (c + 1) < max_lines && HostLive.get('content.length') > 0 ) {
- var t = HostLive.get('content.length');
- c++;
- tooltip += ' Live Hosts';
- for(var i=0; i < t; i++) {
- var host = HostLive.get('content.' + i),
- stream = host && host.target;
- if ( ! stream )
- continue;
-
- c += 1;
- if ( c > max_lines ) {
- var sc = 1 + (streams && streams.length || 0);
- tooltip += 'And ' + utils.number_commas(t - (max_lines - sc)) + ' more... ';
- break;
- }
-
- var hosting;
- if ( ! host.ffz_hosts || host.ffz_hosts.length === 1 )
- hosting = host.display_name;
- else
- hosting = utils.number_commas(host.ffz_hosts.length);
-
- tooltip += (i === 0 ? ' ' : '') +
- '' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + ' ' +
- hosting + ' hosting ' + utils.sanitize(stream.channel.display_name || stream.channel.name) + ' ' +
- '' + (stream.meta_game ? 'Playing ' + utils.sanitize(stream.meta_game) : 'Not Playing') + ' ';
- }
- }*/
-
- // Reposition the tooltip.
- /*setTimeout(function() {
- var tip = document.querySelector('.tipsy');
- if ( ! tip )
- return;
-
- var bb = tip.getBoundingClientRect(),
-
- left = parseInt(tip.style.left || '0'),
- right = bb.left + tip.scrollWidth;
-
- if ( bb.left < 5 )
- tip.style.left = (left - bb.left) + 5 + 'px';
- else if ( right > document.body.clientWidth - 5 )
- tip.style.left = (left - (5 + right - document.body.clientWidth)) + 'px';
- });*/
-
- return tooltip;
-}
-
-
-FFZ.prototype._install_following_tooltips = function() {
- var f = this,
- data = {
- html: true,
- delayOut: 100,
- className: function() { return WIDE_TIP(f, this); },
- title: function() { return f._build_following_tooltip(this); },
- gravity: function(box) {
- return (box ?
- utils.newtip_placement :
- utils.tooltip_placement)(constants.TOOLTIP_DISTANCE, 'w').call(this, box);
- //utils.tooltip_placement(constants.TOOLTIP_DISTANCE * 2, 'w')
- }
- };
-
- for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
- var following = jQuery(FOLLOWING_CONTAINERS[i][0]);
- if ( following && following.length ) {
- var dat = _.extend(data, {prependTo: FOLLOWING_CONTAINERS[i][1] || document.body});
- var td = following.data('tipsy');
- if ( td && td.options ) {
- td.options = _.extend(td.options, dat);
- } else
- following.zipsy(dat);
- }
- }
-}
-
-
-FFZ.prototype._draw_following_channels = function(streams, total) {
- this._tooltip_streams = streams;
- this._tooltip_total = total;
-}
-
-
-FFZ.prototype._draw_following_count = function(count) {
- count = count ? utils.format_unread(count) : '';
- for(var i=0; i < FOLLOWING_CONTAINERS.length; i++) {
- var container = document.querySelector(FOLLOWING_CONTAINERS[i][0]),
- badge = container && container.querySelector('.ffz-follow-count');
- if ( ! container )
- continue;
-
- if ( this.has_bttv || ! this.settings.following_count ) {
- badge && container.removeChild(badge);
- continue;
-
- } else if ( ! badge ) {
- badge = utils.createElement('span', 'ffz-follow-count');
- if ( container.classList.contains('top-nav__nav-link') )
- badge.className += ' flex flex--horizontalCenter flex--verticalCenter pill';
-
- container.appendChild(badge);
- }
-
- badge.innerHTML = count;
- }
-}
\ No newline at end of file
diff --git a/src/ui/following.js b/src/ui/following.js
deleted file mode 100644
index 182bfaa3..00000000
--- a/src/ui/following.js
+++ /dev/null
@@ -1,335 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants'),
-
- VALID_CHANNEL = /^[A-Za-z0-9_]+$/,
- TWITCH_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)/i;
-
-
-// ---------------
-// Initialization
-// ---------------
-
-FFZ.prototype.setup_following = function() {
- this.log("Initializing following support.");
- this.follow_data = {};
- this.follow_sets = {};
-}
-
-
-// ---------------
-// Settings
-// ---------------
-
-FFZ.settings_info.follow_buttons = {
- type: "boolean",
- value: true,
- no_mobile: true,
-
- category: "Channel Metadata",
- name: "Featured Channels",
- help: 'Display additional Follow buttons for channels featured by the stream, such as people participating in co-operative gameplay.',
- on_update: function(val) {
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('following');
- }
-};
-
-
-// ---------------
-// Command
-// ---------------
-
-FFZ.ffz_commands.following = function(room, args) {
- args = args.join(" ").trim().toLowerCase().split(/[ ,]+/);
-
- var f = this,
- out = [];
-
- for(var i=0,l=args.length; i'),
-
- channel = {
- name: user_id
- },
-
- is_following = null,
- is_notified = false,
-
- update = function() {
- if ( channel.logo )
- avatar.src = channel.logo;
-
- var name = f.format_display_name(channel.display_name || user_id, user_id);
- name_el.innerHTML = name[0];
- name_el.setAttribute('original-title', name[1] || '');
-
- el.setAttribute('data-loaded', is_following !== null);
- el.setAttribute('data-following', is_following);
-
- btn_follow.textContent = is_following ? 'Unfollow' : 'Follow';
- btn_follow.classList.toggle('is-following', is_following);
- btn_follow.classList.toggle('button--status', is_following);
- sw_notif.classList.toggle('active', is_notified);
- sw_notif.setAttribute('original-title', 'Notify me when ' + name[0] + ' goes live.');
- },
-
- check_following = function() {
- // Minimize our API calls.
- utils.api.get("users/:login/follows/channels/" + user_id)
- .done(function(data) {
- is_following = true;
- is_notified = data.notifications;
- channel = data.channel;
- update();
-
- }).fail(function() {
- utils.api.get("channels/" + user_id)
- .done(function(data) {
- is_following = false;
- is_notified = false;
- channel = data;
- update();
- }).fail(function() {
- el.removeChild(btn_follow);
- el.removeChild(sw_notif);
- el.appendChild(utils.createElement('span', 'right', 'Invalid Channel'));
- });
- });
- },
-
- do_follow = function(notice) {
- if ( notice !== false )
- notice = true;
-
- is_following = true;
- is_notified = notice;
- update();
-
- return utils.api.put("users/:login/follows/channels/" + user_id, {notifications: notice}, undefined, user.chat_oauth_token)
- .fail(check_following);
- };
-
- btn_follow.addEventListener('click', function() {
- is_following = is_notified = ! is_following;
- update();
-
- f.ws_send("track_follow", [user_id, is_following]);
-
- if ( is_following )
- do_follow();
- else
- utils.api.del("users/:login/follows/channels/" + user_id, undefined, undefined, user.chat_oauth_token)
- .fail(check_following);
- });
-
- sw_notif.addEventListener('click', function() {
- do_follow(!is_notified);
- });
-
- el.setAttribute('data-user', user_id);
-
- name_el.href = 'https://www.twitch.tv/' + user_id;
- name_el.target = '_blank';
-
- el.appendChild(avatar);
- el.appendChild(name_el);
- if ( user ) {
- el.appendChild(btn_follow);
- el.appendChild(sw_notif);
- } else
- this.log("No User", user);
- container.appendChild(el);
-
- check_following();
- update();
- }
-}
\ No newline at end of file
diff --git a/src/ui/logviewer.js b/src/ui/logviewer.js
deleted file mode 100644
index ca0ea742..00000000
--- a/src/ui/logviewer.js
+++ /dev/null
@@ -1,893 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants'),
-
- BAN_REGEX = /^<([^ ]+) has been (banned|timed out|unbanned)(?: for ([^.]+))?\.?(?: Reasons?: ([^(>]+))?(?: ?\((\d+) times\))?>$/;
-
-
-// ----------------
-// Token Request
-// ----------------
-
-FFZ.prototype._lv_token_requests = null;
-FFZ.prototype._lv_token = {"token": null, "expires": 0};
-
-FFZ.prototype.lv_get_token = function() {
- var f = this,
- token = this._lv_token,
- now = Date.now() / 1000;
-
- return new Promise(function(succeed, fail) {
- // If we're not logged in, we can't get a token.
- var user = f.get_user();
- if ( ! user || ! user.login )
- return fail(null);
-
- // Make sure the token will be valid for at least 5 more minutes.
- if ( token.token && token.expires > (now + 300) )
- return succeed(token.token);
-
- // If we're already making a request, don't duplicate it.
- if ( f._lv_token_requests )
- return f._lv_token_requests.push([succeed, fail]);
-
- // Nope, new request.
- f._lv_token_requests = [[succeed, fail]];
-
- f.ws_send("get_logviewer_token", undefined, function(succeeded, data) {
- token = succeeded ? data : token = {"token": null, "expires": 0};
-
- var requests = f._lv_token_requests;
- f._lv_token = token;
- f._lv_token_requests = null;
-
- for(var i=0; i < requests.length; i++) {
- requests[i][succeeded ? 0 : 1](token.token);
- }
-
- if ( succeeded && f._lv_ws_open )
- f._lv_ws_sock.send('42' + JSON.stringify(["token", token]));
-
- }, true);
- });
-}
-
-
-// ----------------
-// Log Requests
-// ----------------
-
-FFZ.prototype.lv_get_logs = function(room_id, user_id, ref_id, before_cnt, after_cnt) {
- var f = this;
- return new Promise(function(succeed, fail) {
- f.lv_get_token().then(function(token) {
- args = [];
-
- user_id !== undefined && user_id !== null && args.push('nick=' + user_id);
- ref_id !== undefined && ref_id !== null && args.push('id=' + ref_id);
- before_cnt !== undefined && before_cnt !== null && args.push('before=' + before_cnt);
- after_cnt !== undefined && after_cnt !== null && args.push('after=' + after_cnt)
-
- utils.logviewer.get("logs/" + room_id + "?" + args.join('&'), token)
- .then(utils.json).then(function(data) {
- // Parse every message immediately.
- if ( data ) {
- var bound = f.lv_parse_message.bind(f);
- if ( data.before )
- data.before = _.map(data.before, bound);
- if ( data.after )
- data.after = _.map(data.after, bound);
- }
-
- succeed(data);
- });
- });
- })
-}
-
-
-// ----------------
-// Message Processing
-// ----------------
-
-FFZ.prototype.lv_parse_message = function(message) {
- var parsed = utils.parse_irc_privmsg(message.text),
- ffz_room = this.rooms && this.rooms[parsed.room],
- room = ffz_room && ffz_room.room;
-
- parsed.lv_id = message.id;
- parsed.mod_logs = message.modlog;
- parsed.date = typeof message.time === "number" ? new Date(message.time * 1000) : utils.parse_date(message.time);
-
- // Is this administrative?
- parsed.is_admin = parsed.tags['display-name'] === 'jtv';
- if ( parsed.is_admin ) {
- parsed.style = 'admin';
- delete parsed.tags['display-name'];
- }
-
-
- // Is this a notification?
- parsed.is_notice = parsed.tags['display-name'] === 'twitchnotify';
- if ( parsed.is_notice ) {
- parsed.style = 'notification';
- delete parsed.tags['display-name'];
- }
-
-
- // Is this a ban?
- var match = parsed.is_admin && BAN_REGEX.exec(parsed.message);
- if ( match ) {
- var unban = match[2] === 'unbanned',
- duration = unban ? -Infinity : match[2] === 'banned' ? Infinity : utils.parse_lv_duration(match[3]),
- reasons = match[4] ? match[4].trim().split(/\s*,\s*/) : [],
- ban_count = match[5] && parseInt(match[5]) || 1;
-
- parsed.message = this.format_ban_notice(
- parsed.from, false, duration, ban_count, reasons,
- parsed.mod_logs && Object.keys(parsed.mod_logs),
- parsed.mod_logs);
-
- parsed.cachedTokens = [{type: "raw", html: parsed.message}];
-
- } else if ( parsed.from === "jtv" && parsed.mod_logs ) {
- parsed.message = Object.keys(parsed.mod_logs).join(", ") + " used: " + parsed.message;
- parsed.cachedTokens = [{type: "text", text: parsed.message}];
- }
-
- if ( parsed.tags.color )
- parsed.color = parsed.tags.color;
- else {
- var tmiSession = room && room.tmiSession || window.TMI && TMI._sessions && TMI._sessions[0];
- parsed.color = tmiSession && parsed.from ? tmiSession.getColor(parsed.from) : "#755000";
- }
-
- if ( ! parsed.tags.hasOwnProperty('mod') ) {
- var badges = parsed.tags.badges || {};
- parsed.tags.mod = parsed.from === parsed.room || badges.hasOwnProperty('staff') || badges.hasOwnProperty('admin') || badges.hasOwnProperty('global_mod') || badges.hasOwnProperty('moderator');
- }
-
- if ( ! parsed.cachedTokens )
- this.tokenize_chat_line(parsed, true, room && room.get('roomProperties.hide_chat_links'));
-
- return parsed;
-}
-
-
-// ---------------------
-// WebSocket Connection
-// ---------------------
-
-FFZ.prototype.lv_ws_create = function() {
- var f = this, ws,
- server = constants.LV_SOCKET_SERVER + '?EIO=3&transport=websocket';
-
- if ( this._lv_ws_recreate_timer ) {
- clearTimeout(this._lv_ws_recreate_timer);
- this._lv_ws_recreate_timer = null;
- }
-
- if ( this._lv_ws_connecting || this._lv_ws_open )
- return;
-
- this._lv_ws_topics = this._lv_ws_topics || [];
- this._lv_ws_ping_timer = null;
- this._lv_ws_connecting = true;
-
- this.log('[LV] Using Socket Server: ' + server);
-
- try {
- ws = this._lv_ws_sock = new WebSocket(server);
- } catch(err) {
- this._lv_ws_sock = null;
- return this.error('[LV] Error creating WebSocket', err);
- }
-
- ws.onopen = function(e) {
- f._lv_ws_connecting = false;
- f._lv_ws_open = true;
- f.log('[LV] Socket connected.');
-
- ws.send('2probe');
- ws.send('5');
-
- if ( f._lv_token.token )
- ws.send('42' + JSON.stringify(["token", f._lv_token.token]));
-
- // Ping every 10 seconds just to be safe.
- f._lv_ws_ping_timer = setInterval(function() {
- ws.send('2');
- }, 10000);
-
- if ( f._lv_ws_topics.length )
- for(var i=0; i < f._lv_ws_topics.length; i++)
- ws.send('42' + JSON.stringify(["subscribe", f._lv_ws_topics[i]]));
- else
- f._lv_ws_close_timer = setTimeout(f.lv_ws_maybe_close.bind(f), 5000);
- }
-
- ws.onclose = function(e) {
- var was_open = f._lv_ws_open;
- f.log('[LV] Socket closed. (Code: ' + e.code + ', Reason: ' + e.reason + ')');
-
- f._lv_ws_open = false;
- f._lv_ws_connecting = false;
- f._lv_ws_sock = null;
- if ( f._lv_ws_ping_timer ) {
- clearInterval(f._lv_ws_ping_timer);
- f._lv_ws_ping_timer = null;
- }
-
- // Do we care about reconnecting? We only do if we have topics?
- if ( ! f._lv_ws_topics || ! f._lv_ws_topics.length )
- return;
-
- f._lv_ws_recreate_timer = setTimeout(f.lv_ws_create.bind(f), 500 + Math.random() * 2000);
- }
-
- ws.onmessage = function(e) {
- var message = e.data;
- // We only care about the meaning of life.
- if ( message.substr(0,2) !== '42' )
- return;
-
- var data = JSON.parse(message.substr(2));
-
- if ( f._lv_ws_callbacks && f._lv_ws_callbacks.length )
- for(var i=0; i < f._lv_ws_callbacks.length; i++)
- f._lv_ws_callbacks[i](data[0], data[1]);
- }
-}
-
-
-FFZ.prototype.lv_ws_add_callback = function(callback) {
- this._lv_ws_callbacks = this._lv_ws_callbacks || [];
- if ( this._lv_ws_callbacks.indexOf(callback) === -1 )
- this._lv_ws_callbacks.push(callback);
-}
-
-
-FFZ.prototype.lv_ws_remove_callback = function(callback) {
- this._lv_ws_callbacks = this._lv_ws_callbacks || [];
- var ind = this._lv_ws_callbacks.indexOf(callback);
- if ( ind !== -1 )
- this._lv_ws_callbacks.splice(ind, 1);
-}
-
-
-FFZ.prototype.lv_ws_sub = function(topic) {
- this._lv_ws_topics = this._lv_ws_topics || [];
- if ( this._lv_ws_topics.indexOf(topic) !== -1 )
- return;
-
- this._lv_ws_topics.push(topic);
- if ( this._lv_ws_close_timer ) {
- clearTimeout(this._lv_ws_close_timer);
- this._lv_ws_close_timer = null;
- }
-
- if ( this._lv_ws_open )
- this._lv_ws_sock.send("42" + JSON.stringify(['subscribe', topic]));
-
- else if ( ! this._lv_ws_connecting )
- this.lv_ws_create();
-}
-
-FFZ.prototype.lv_ws_unsub = function(topic) {
- this._lv_ws_topics = this._lv_ws_topics || [];
- var ind = this._lv_ws_topics.indexOf(topic);
- if ( ind === -1 )
- return;
-
- this._lv_ws_topics.splice(ind, 1);
-
- if ( this._lv_ws_open ) {
- this._lv_ws_sock.send("42" + JSON.stringify(['unsubscribe', topic]));
- if ( this._lv_ws_topics.length === 0 )
- this._lv_ws_close_timer = setTimeout(this.lv_ws_maybe_close.bind(this), 5000);
- }
-}
-
-
-FFZ.prototype.lv_ws_maybe_close = function() {
- if ( this._lv_ws_close_timer ) {
- clearTimeout(this._lv_ws_close_timer);
- this._lv_ws_close_timer = null;
- }
-
- if ( (! this._lv_ws_topics || ! this._lv_ws_topics.length) && this._lv_ws_open )
- this._lv_ws_sock.close();
-}
-
-
-// ----------------
-// Moderation Card Pages
-// ----------------
-
-FFZ.mod_card_pages = {};
-
-FFZ.mod_card_pages.default = {
- title: "C ontrols",
- render: function(mod_card, el) { }
-}
-
-FFZ.mod_card_pages.history = {
- title: "Chat H istory",
- needs_lv: true,
-
- render_more: function(mod_card, el, history, ref_id, is_user, is_after) {
- var f = this,
- controller = utils.ember_lookup('controller:chat'),
- user_id = mod_card.get('cardInfo.user.id'),
- room_id = controller && controller.get('currentRoom.id'),
-
- btn_more = utils.createElement('li', 'button ffz-load-more' + (is_after ? ' load-after' : ''), ' Load More ');
-
- if ( is_after )
- history.appendChild(btn_more);
- else
- history.insertBefore(btn_more, history.firstElementChild);
-
- btn_more.addEventListener('click', function() {
- history.scrollTop = 0;
- history.classList.add('loading');
- f.lv_get_logs(room_id, is_user ? user_id : null, ref_id, is_after ? 0 : 10, is_after ? 10 : 0).then(function(data) {
- history.removeChild(btn_more);
- history.classList.remove('loading');
-
- var messages = is_after ? data.after : data.before,
- last_message = history.querySelector('.chat-line:' + (is_after ? 'last' : 'first') + '-of-type'),
- last_date = last_message ? last_message.getAttribute('data-date') : (new Date).toLocaleDateString();
-
- if ( last_message.classList.contains('timestamp-line') )
- last_message.parentElement.removeChild(last_message);
-
- if ( ! is_after )
- messages.reverse();
-
- var original_message = history.querySelector('.original-msg'),
- original_sender = original_message && original_message.getAttribute('data-sender');
-
- for(var i=0; i < messages.length; i++) {
- var new_message = messages[i],
- date = new_message.date.toLocaleDateString(),
- date_line = null;
-
- new_message.original_sender = original_sender === new_message.from;
-
- var new_line = f._build_mod_card_history(
- new_message, mod_card, !is_user,
- FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, new_message)
- );
-
- if ( is_user )
- new_line.classList.remove('ffz-mentioned');
-
- new_message.original_sender = null;
-
- if ( last_date !== date ) {
- date_line = utils.createElement('li', 'chat-line timestamp-line', is_after ? date : last_date);
- date_line.setAttribute('data-date', is_after ? date : last_date);
- last_date = date;
- if ( is_after )
- history.appendChild(date_line);
- else
- history.insertBefore(date_line, history.firstElementChild);
- }
-
- if ( is_after )
- history.appendChild(new_line);
- else
- history.insertBefore(new_line, history.firstElementChild);
- }
-
- if ( ! is_after && last_date !== (new Date).toLocaleDateString() ) {
- var date_line = utils.createElement('li', 'chat-line timestamp-line', last_date);
- date_line.setAttribute('data-date', last_date);
- history.insertBefore(date_line, history.firstElementChild);
- }
-
- // Only add the button back if there are even more messages to load.
- if ( messages.length >= 10 )
- if ( is_after )
- history.appendChild(btn_more);
- else
- history.insertBefore(btn_more, history.firstElementChild);
-
- var original = history.querySelector('.chat-line[data-lv-id="' + ref_id + '"]');
- if ( original )
- setTimeout(function() {
- history.scrollTop = (original.offsetTop - history.offsetTop) - (history.clientHeight - original.clientHeight) / 2;
- });
-
- ref_id = messages[messages.length-1].lv_id;
- });
- })
- },
-
- render_adjacent: function(mod_card, el, message) {
- var f = this,
- controller = utils.ember_lookup('controller:chat'),
- user_id = mod_card.get('cardInfo.user.id'),
- room_id = controller && controller.get('currentRoom.id'),
- ffz_room = this.rooms[room_id],
-
- old_history = el.querySelector('.chat-history'),
- history = el.querySelector('.adjacent-history');
-
- old_history.classList.add('hidden');
-
- if ( history )
- history.innerHTML = '';
- else {
- var btn_hide = utils.createElement('li', 'button ffz-back-button', ' Back'),
- btn_container = utils.createElement('ul', 'moderation-card__actions chat-history chat-back-button', btn_hide);
-
- btn_hide.addEventListener('click', function() {
- el.removeChild(history);
- el.removeChild(btn_container);
- old_history.classList.remove('hidden');
- })
-
- history = utils.createElement('ul', 'moderation-card__actions chat-history adjacent-history');
- el.appendChild(btn_container);
- el.appendChild(history);
- }
-
- history.classList.add('loading');
-
- f.lv_get_logs(room_id, null, message.lv_id, 10, 10).then(function(data) {
- history.classList.remove('loading');
-
- // Should we display more?
- if ( data.before.length >= 10 )
- FFZ.mod_card_pages.history.render_more.call(
- f, mod_card, el, history, data.before[0].lv_id, false, false);
-
- var last_date = (new Date).toLocaleDateString(),
- messages = _.union(data.before, [message], data.after);
-
- for(var i=0; i < messages.length; i++) {
- var new_message = messages[i],
- date = new_message.date.toLocaleDateString();
-
- if ( date !== last_date ) {
- var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
- date_line.setAttribute('data-date', date);
- history.appendChild(date_line);
- last_date = date;
- }
-
- new_message.is_original = new_message.lv_id === message.lv_id;
- new_message.original_sender = new_message.from === message.from;
-
- var msg_line = f._build_mod_card_history(new_message, mod_card, true,
- FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, new_message));
-
- if ( new_message.is_original )
- msg_line.classList.remove('ffz-mentioned');
-
- history.appendChild(msg_line);
-
- // These objects can be persistent, so clear these.
- new_message.is_original = null;
- new_message.original_sender = null;
- }
-
- if ( data.after.length >= 10 )
- FFZ.mod_card_pages.history.render_more.call(
- f, mod_card, el, history, data.after[data.after.length-1].lv_id, false, true);
-
- setTimeout(function() {
- var original = history.querySelector('.original-msg');
- if ( original )
- history.scrollTop = (original.offsetTop - history.offsetTop) - (history.clientHeight - original.clientHeight) / 2;
- })
- });
- },
-
- render: function(mod_card, el) {
- var f = this,
- controller = utils.ember_lookup('controller:chat'),
- user_id = mod_card.get('cardInfo.user.id'),
- room_id = controller && controller.get('currentRoom.id'),
- ffz_room = this.rooms[room_id],
-
- history = utils.createElement('ul', 'moderation-card__actions chat-history lv-history');
-
- el.appendChild(history);
-
- // Are we relying on LogViewer here?
- if ( ! ffz_room.has_logs || ! mod_card.lv_view ) {
- history.innerHTML = 'You do not have permission to view chat history in this channel. ';
- return;
- }
-
- // Start loading!
- history.classList.add('loading');
-
- mod_card.lvGetLogs().then(function(data) {
- history.classList.remove('loading');
-
- // Should we display more?
- if ( data.before.length >= 10 )
- FFZ.mod_card_pages.history.render_more.call(
- f, mod_card, el, history, data.before[0].lv_id, true, false);
-
- var last_date = (new Date).toLocaleDateString();
-
- if ( ! data.before.length )
- history.innerHTML = '(There are no logged messages to display.) ';
-
- for(var i=0; i < data.before.length; i++) {
- var message = data.before[i],
- date = message.date.toLocaleDateString();
-
- if ( date !== last_date ) {
- var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
- date_line.setAttribute('data-date', date);
- history.appendChild(date_line);
- last_date = date;
- }
-
- var msg_line = f._build_mod_card_history(message, mod_card, false,
- FFZ.mod_card_pages.history.render_adjacent.bind(f, mod_card, el, message));
-
- msg_line.classList.remove('ffz-mentioned');
- history.appendChild(msg_line);
- }
-
- FFZ.mod_card_pages.notes.update_counter(mod_card, data.comments.length)
-
- history.scrollTop = history.scrollHeight;
- });
- }
-}
-
-FFZ.mod_card_pages.stats = {
- title: "S tats",
- render: function(mod_card, el) {
- var f = this;
-
- utils.render_update_list(this, this._sorted_blocks(FFZ.mod_stats_blocks), el, 'moderation-card__actions', true);
- }
-};
-
-
-FFZ.mod_stats_blocks = {
- basics: {
- refresh: false,
- type: 'list',
-
- render: function() {
- var f = this,
- mod_card = this._mod_card,
- user_id = mod_card.get('cardInfo.user._id'),
- controller = utils.ember_lookup('controller:chat'),
- room = controller && controller.get('currentRoom');
-
- return new Promise(function(succeed, fail) {
- var room_id = room.get('roomProperties._id'),
- room_name = room.get('channel.display_name') || room.get('name');
- if ( room.get('isGroupRoom') || ! room_id )
- succeed([
- ['User ID', user_id]
- ]);
-
- utils.api.get("users/" + user_id + "/follows/channels/" + room_id, null, {version: 5}).done(function(data) {
- var now = Date.now() - (f._ws_server_offset || 0),
- since = utils.parse_date(data.created_at),
- age = Math.floor((now - since.getTime()) / 1000);
-
- succeed([
- ['User ID', user_id],
- ['Follows ' + utils.sanitize(room_name), '' + utils.human_time(age, 10) + ' ']
- ]);
- }).fail(function() {
- succeed([
- ['User ID', user_id],
- ['Does not follow ' + utils.sanitize(room_name), '']
- ]);
- });
- });
- }
- },
-
- logviewer: {
- refresh: 1000,
- type: 'list',
-
- render: function() {
- var mod_card = this._mod_card,
- controller = utils.ember_lookup('controller:chat'),
- room_id = controller && controller.get('currentRoom.id'),
- user_name = mod_card.get('cardInfo.user.id'),
- ffz_room = this.rooms && this.rooms[room_id];
-
- if ( ! ffz_room.has_logs || ! mod_card.lv_view )
- return [];
-
- return new Promise(function(succeed) {
- mod_card.lvGetLogs().then(function(data) {
- succeed([
- ['Messages', utils.number_commas(data.user.messages || 0)],
- ['Timeouts', utils.number_commas(data.user.timeouts || 0)],
- ['Bans', utils.number_commas(data.user.bans || 0)],
- ['Chat Log Source', 'CBenni\'s Logviewer ']
- ]);
- })
- });
- }
- }
-
-}
-
-
-FFZ.mod_card_pages.notes = {
- title: "N otes",
- needs_lv: true,
-
- update_counter: function(mod_card, count) {
- var el = mod_card.get('element'),
- notes_tab = el && el.querySelector('.moderation-card__actions [data-page=notes]');
-
- if ( notes_tab )
- notes_tab.innerHTML = FFZ.mod_card_pages.notes.title +
- (count > 0 ? ' ' + utils.number_commas(count) + '
' : '');
- },
-
- add_note: function(mod_card, el, note, history, last_line, do_scroll) {
- if ( ! history )
- history = el.querySelector('.chat-history.user-notes');
-
- if ( ! history )
- return;
-
- if ( ! last_line )
- last_line = history.querySelector('.chat-line:last-of-type');
-
- var was_at_bottom = do_scroll ? history.scrollTop >= (history.scrollHeight - history.clientHeight) : false,
- last_date = last_line ? last_line.getAttribute('data-date') : (new Date).toLocaleDateString();
-
- if ( last_line && last_line.classList.contains('no-messages') ) {
- last_line.parentElement.removeChild(last_line);
- last_line = null;
- }
-
- var output = FFZ.mod_card_pages.notes.build_note.call(this, mod_card, note),
- date = output.getAttribute('data-date');
-
- if ( last_date !== date ) {
- var date_line = utils.createElement('li', 'chat-line timestamp-line', date);
- date_line.setAttribute('data-date', date);
- history.appendChild(date_line);
- }
-
- history.appendChild(output);
- FFZ.mod_card_pages.notes.update_counter(mod_card, history.querySelectorAll(".message-line").length);
-
- if ( was_at_bottom )
- setTimeout(function() { history.scrollTop = history.scrollHeight });
-
- return output;
- },
-
- build_note: function(mod_card, note) {
- var f = this,
- controller = utils.ember_lookup('controller:chat'),
- room = controller && controller.get('currentRoom'),
- user = this.get_user(),
- tmiSession = room.tmiSession || (window.TMI && TMI._sessions && TMI._sessions[0]),
-
- message = {
- date: new Date(note.added * 1000),
- from: note.author,
- room: note.channel,
- lv_id: note.id,
- message: note.text,
- tags: {
- 'display-name': FFZ.get_capitalization(note.author)
- },
- color: tmiSession && note.author ? tmiSession.getColor(note.author) : "#755000"
- };
-
- this.tokenize_chat_line(message, true, false);
-
- var can_edit = false, // mod_card.lv_write_notes && user && user.login === message.from,
- can_delete = mod_card.lv_delete_notes || (user && user.login === message.from),
- can_mod = can_edit || can_delete;
-
- var output = this._build_mod_card_history(message, mod_card, true, false, can_mod);
-
- if ( can_mod ) {
- var mod_icons = output.querySelector('.mod-icons');
- if ( can_delete ) {
- var btn_delete = utils.createElement('a', 'mod-icon html-tooltip delete-note', 'Delete');
- btn_delete.title = 'Delete Note';
- mod_icons.appendChild(btn_delete);
-
- btn_delete.addEventListener('click', function(e) {
- if ( e.ctrlKey || e.metaKey || e.altKey || btn_delete.classList.contains('loading') )
- return;
-
- btn_delete.classList.add('loading');
-
- f.lv_get_token().then(function(token) {
- utils.logviewer.del("comments/" + note.channel + "?id=" + note.id, token)
- .then(function(resp) {
- btn_delete.classList.remove('loading');
- if ( resp.ok )
- mod_card.lvOnMessage("comment-delete", note);
- else
- alert("An error occured deleting this note.");
- }).catch(function() {
- btn_delete.classList.remove('loading');
- alert("An error occured deleting this note.");
- });
-
- }).catch(function() {
- btn_delete.classList.remove('loading');
- alert("An error occured deleting this note.");
- })
-
- e.preventDefault();
- e.stopPropagation();
- });
- }
-
- /*if ( can_edit ) {
- var btn_edit = utils.createElement('a', 'mod-icon html-tooltip edit-note', 'Edit');
- btn_edit.title = 'Edit Note';
- mod_icons.appendChild(btn_edit);
- }*/
- }
-
- return output;
- },
-
- render: function(mod_card, el) {
- var f = this,
- controller = utils.ember_lookup('controller:chat'),
- room = controller && controller.get('currentRoom'),
- tmiSession = room.tmiSession || (window.TMI && TMI._sessions && TMI._sessions[0]),
-
- room_id = room && room.get('id'),
- user_id = mod_card.get('cardInfo.user.id'),
-
- ffz_room = this.rooms[room_id],
- history = utils.createElement('ul', 'moderation-card__actions chat-history user-notes');
-
- el.appendChild(history);
-
- if ( ! ffz_room.has_logs || ! mod_card.lv_view_notes ) {
- history.innerHTML = 'You do not have permission to view notes in this channel. ';
- return;
- }
-
- history.classList.add('loading');
-
- this.lv_get_token().then(function(token) {
- utils.logviewer.get("comments/" + room_id + "?topic=" + user_id, token)
- .then(utils.json).then(function(data) {
-
- //f.log("[LV] Comments: " + user_id + " in " + room_id, data);
- history.classList.remove('loading');
-
- // We want to listen to get new notes for this user.
- mod_card._lv_sock_room = room_id;
- mod_card._lv_sock_user = user_id;
- f.lv_ws_sub('logs-' + room_id + '-' + user_id);
-
- FFZ.mod_card_pages.notes.update_counter(mod_card, data.length);
-
- if ( data.length ) {
- var last_line = null;
- for(var i=0; i < data.length; i++)
- last_line = FFZ.mod_card_pages.notes.add_note.call(f, mod_card, el, data[i], history, last_line, false);
- }
-
- else
- history.appendChild(utils.createElement('li', 'chat-line message-line admin no-messages',
- 'There are no notes on this user. '));
- });
- });
-
- if ( mod_card.lv_write_notes ) {
- var textarea = utils.createElement('textarea', 'chat_text_input mousetrap form__input note-text-input'),
- note_container = utils.createElement('div', 'moderation-card__actions textarea-contain note-input', textarea),
- btn_submit = utils.createElement('a', 'button float-right', 'Add Note'),
- btn_container = utils.createElement('div', 'chat-buttons-container clearfix', btn_submit),
-
- submit_note = function() {
- if ( note_container.classList.contains('loading') )
- return;
-
- var comment = textarea.value.trim();
- if ( ! comment.length )
- return;
-
- note_container.classList.add('loading');
- textarea.disabled = btn_submit.disabled = true;
-
- f.lv_get_token().then(function(token) {
- utils.logviewer.post("comments/" + room_id, null, {
- headers: {
- 'Content-Type': 'application/json;charset=UTF-8'
- },
- body: JSON.stringify({
- comment: {
- topic: user_id,
- text: comment
- },
- token: token
- })
- }).then(function(resp) {
- note_container.classList.remove('loading');
- textarea.disabled = btn_submit.disabled = false;
- textarea.value = '';
-
- if ( resp.ok )
- textarea.placeholder = 'The note was posted successfully. Write another note...';
- else
- alert("An error occured posting this note.");
-
- }).catch(function() {
- note_container.classList.remove('loading');
- textarea.disabled = btn_submit.disabled = false;
- alert("An error occured posting this note.");
- });
-
- }).catch(function() {
- note_container.classList.remove('loading');
- textarea.disabled = btn_submit.disabled = false;
- alert("An error occured posting this note.");
- })
-
- };
-
- textarea.placeholder = 'Write a note about ' + user_id + '...';
-
- btn_submit.addEventListener('click', submit_note);
- note_container.appendChild(btn_container);
- el.appendChild(note_container);
- }
- }
-}
-
-FFZ.mod_card_pages.name_history = {
- title: "Name History ",
-
- render: function(mod_card, el) {
- var f = this,
- user_id = mod_card.get('cardInfo.user._id'),
-
- history = utils.createElement('ul', 'moderation-card__actions chat-history lv-history');
-
- el.appendChild(history);
-
- // Start loading!
- history.classList.add('loading');
-
- f.ws_send("get_name_history", user_id, function(success, data) {
- history.classList.remove('loading');
-
- if ( success ) {
- for(var i=0; i < data.length; i++) {
- var changed_at = data[i][0] && utils.parse_date(data[i][0]),
- changed = changed_at ?
- (Date.now() - changed_at.getTime()) > 86400000 ?
- changed_at.toLocaleDateString() :
- changed_at.toLocaleTimeString()
- :
- i === 0 ? 'Initial' : 'Unknown';
-
- history.appendChild(utils.createElement('li', 'chat-line message-line admin no-messages',
- '' + utils.sanitize(changed) + ': ' +
- '' + utils.sanitize(data[i][1]) + ' '));
- }
- } else
- history.appendChild(utils.createElement('li', 'chat-line message-line admin no-messages',
- 'Unable to load username history for this user. '));
- });
- }
-}
\ No newline at end of file
diff --git a/src/ui/menu.js b/src/ui/menu.js
deleted file mode 100644
index 4eab55ab..00000000
--- a/src/ui/menu.js
+++ /dev/null
@@ -1,850 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils'),
-
- IS_OSX = constants.IS_OSX,
-
- reported_sets = [],
-
- fix_menu_position = function(container) {
- var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait');
-
- var bounds = container.children[0].getBoundingClientRect(),
- left = parseInt(container.style.left || '0'),
- right = bounds.left + bounds.width,
- moved = !!container.style.left;
-
- if ( swapped ) {
- if ( bounds.left < 20 ) {
- container.style.left = '';
- moved = false;
- } else if ( right > document.body.clientWidth )
- container.style.left = (left - (right - document.body.clientWidth)) + 'px';
-
- } else {
- if ( bounds.left < 0 )
- container.style.left = (left - bounds.left) + 'px';
- else if ( right > (document.body.clientWidth - 20) ) {
- container.style.left = '';
- moved = false;
- }
- }
-
- container.classList.toggle('ui-moved', moved);
- };
-
-
-// --------------------
-// Initializer
-// --------------------
-
-FFZ.prototype.setup_menu = function() {
- document.body.classList.toggle("ffz-menu-replace", this.settings.replace_twitch_menu);
-
- // Add FFZ to the chat settings menu.
- this.update_views('component:chat/chat-settings-menu', this.modify_chat_settings_menu);
-
- // Maximum Menu Height
- var Layout = utils.ember_lookup('service:layout');
- if ( Layout )
- Layout.addObserver('windowHeight', function() {
- var el = document.querySelector('.ember-chat .chat-settings');
- if ( el )
- el.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px';
- });
-
-}
-
-
-FFZ.prototype.modify_chat_settings_menu = function(component) {
- var f = this,
- Layout = utils.ember_lookup('service:layout');
-
- utils.ember_reopen_view(component, {
- ffz_init: function() {
- var view = this,
- el = this.get('element');
-
- var container = utils.createElement('div', ''),
- header = utils.createElement('div', 'list-header', 'FrankerFaceZ'),
- content = utils.createElement('div', 'chat-menu-content'),
- p, cb, a;
-
- // Dark Twitch
- p = document.createElement('p');
- p.className = 'no-bttv';
- cb = document.createElement('input');
- cb.type = "checkbox";
- cb.className = "ember-checkbox ffz-setting-dark-twitch";
- cb.checked = f.settings.dark_twitch;
- p.appendChild(cb);
- p.appendChild(document.createTextNode("Dark Twitch"));
- content.appendChild(p);
-
- cb.addEventListener("change", function(e) {
- f.settings.set("dark_twitch", this.checked);
- if ( this.checked && localStorage.getItem('ffz_setting_dark_no_blue') === null )
- f.settings.set("dark_no_blue", true);
- });
-
-
- // Channel Hosting
- p = document.createElement('p');
- //p.className = 'no-bttv';
- cb = document.createElement('input');
- cb.type = "checkbox";
- cb.className = "ember-checkbox ffz-setting-hosted-channels";
- cb.checked = f.settings.hosted_channels;
- p.appendChild(cb);
- p.appendChild(document.createTextNode("Channel Hosting"));
- content.appendChild(p);
-
- cb.addEventListener("change", function(e) {
- f.settings.set("hosted_channels", this.checked);
- });
-
-
- // More Settings
- p = document.createElement('p');
- a = document.createElement('a');
- a.href = '#';
- a.innerHTML = 'More Settings';
- p.appendChild(a);
- content.appendChild(p);
-
- a.addEventListener('click', function(e) {
- view.set('isHidden', true);
- f._last_page = 'settings';
- f.build_ui_popup(f._chatv);
- e.stopPropagation();
- return false;
- });
-
- container.appendChild(header);
- container.appendChild(content);
-
- container.classList.toggle('hidden', this.get('showDisplaySettings'));
- el.appendChild(container);
- this.ffz_menu = container;
-
- // Maximum Height
- if ( Layout && el )
- el.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px';
- },
-
- ffz_update_visibility: function() {
- if ( this.ffz_menu )
- this.ffz_menu.classList.toggle('hidden', this.get('showDisplaySettings'));
- }.observes('showDisplaySettings'),
-
- ffz_destroy: function() {
- if ( this.ffz_menu ) {
- this.ffz_menu.parentElement.removeChild(this.ffz_menu);
- this.ffz_menu = null;
- }
- }
- });
-}
-
-
-FFZ.menu_pages = {};
-
-
-// --------------------
-// Create Menu
-// --------------------
-
-FFZ.prototype._fix_menu_position = function() {
- var container = document.querySelector('#ffz-chat-menu');
- if ( container )
- fix_menu_position(container);
-}
-
-FFZ.prototype.build_ui_popup = function(view) {
- var popup = this._popup ? this.close_popup() : this._last_popup;
- if ( popup && popup.id === 'ffz-chat-menu' )
- return;
-
- // Start building the DOM.
- var container = document.createElement('div'),
- inner = document.createElement('div'),
- menu = document.createElement('ul');
-
- container.className = 'emoticon-selector chat-menu ffz-ui-popup';
- container.id = 'ffz-chat-menu';
- inner.className = 'emoticon-selector-box dropmenu';
- container.appendChild(inner);
-
- // Menu Container
- var sub_container = document.createElement('div');
- sub_container.className = 'ffz-ui-menu-page';
-
- inner.appendChild(sub_container);
-
- // Render Menu Tabs
- menu.className = 'menu clearfix';
- inner.appendChild(menu);
-
- var heading = document.createElement('li');
- heading.className = 'title';
- heading.innerHTML = 'Franker' + (constants.DEBUG ? 'Dev' : 'Face') + 'Z ';
-
- // Close Button
- var close_btn = document.createElement('span'),
- f = this;
-
- close_btn.className = 'ffz-handle ffz-close-button';
- heading.insertBefore(close_btn, heading.firstChild);
-
- var can_close = false;
- close_btn.addEventListener('mousedown', function() {
- var popup = f._popup;
- can_close = popup && popup.id === "ffz-chat-menu" && popup.style.left;
- });
-
- close_btn.addEventListener('click', function() {
- var popup = f._popup;
- if ( can_close && popup )
- f.close_popup();
- });
-
- menu.appendChild(heading);
-
- // Draggable
- jQuery(container).draggable({
- handle: menu, cancel: 'li.item', axis:"x",
- stop: function(e) { fix_menu_position(this); }
- });
-
- // Get rid of the position: relative that draggable adds.
- container.style.position = '';
-
- var menu_pages = [];
- for(var key in FFZ.menu_pages) {
- if ( ! FFZ.menu_pages.hasOwnProperty(key) )
- continue;
-
- var page = FFZ.menu_pages[key];
- try {
- if ( !page || (page.hasOwnProperty("visible") && (!page.visible || (typeof page.visible == "function" && !page.visible.call(this, view)))) )
- continue;
- } catch(err) {
- this.error("menu_pages " + key + " visible: " + err);
- continue;
- }
-
- menu_pages.push([page.sort_order || 0, key, page]);
- }
-
- menu_pages.sort(function(a,b) {
- if ( a[0] < b[0] ) return 1;
- else if ( a[0] > b[0] ) return -1;
-
- var al = a[1].toLowerCase(),
- bl = b[1].toLowerCase();
-
- if ( al < bl ) return 1;
- if ( al > bl ) return -1;
- return 0;
- });
-
- for(var i=0; i < menu_pages.length; i++) {
- var key = menu_pages[i][1],
- page = menu_pages[i][2],
- el = document.createElement('li'),
- link = document.createElement('a');
-
- el.className = 'item' + (page.sub_menu || page.pages ? ' has-sub-menu' : '');
- el.id = "ffz-menu-page-" + key;
- link.title = page.name;
- link.innerHTML = page.icon;
-
- jQuery(link).zipsy({gravity: utils.newtip_placement(constants.TOOLTIP_DISTANCE, 'n')});
-
- link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key));
-
- el.appendChild(link);
- menu.appendChild(el);
- }
-
- // Add the menu to the DOM.
- sub_container.style.maxHeight = Math.max(200, view.$().height() - 172) + "px";
- view.$('.chat-interface').append(container);
-
- // Keep track of the pop-up.
- this._popup = container;
- this._popup_allow_parent = true;
-
- // Render Current Page
- var page = (this._last_page || "channel").split("_", 1)[0];
- this._ui_change_page(view, inner, menu, sub_container, page);
-}
-
-
-FFZ.prototype._ui_change_subpage = function(view, inner, menu, container, subpage) {
- var page = this._last_page,
- last_subpages = this._last_subpage = this._last_subpage || {};
-
- last_subpages[page] = subpage;
-
- container.innerHTML = "";
- container.setAttribute('data-page', subpage);
-
- // Get the data structure for this page.
- var page_data = FFZ.menu_pages[page],
- data = page_data.pages[subpage];
-
- // Render the page first. If there's an error, it won't update the other UI stuff.
- data.render.call(this, view, container, inner, menu);
-
- // Make sure the correct menu tab is selected
- jQuery('li.active', menu).removeClass('active');
- jQuery('#ffz-menu-page-' + page + '-subpage-' + subpage, menu).addClass('active');
-
- // Apply wideness - TODO: Revamp wide menus entirely for thin containers
- var is_wide = false,
- app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
-
- if ( data.hasOwnProperty('wide') )
- is_wide = data.wide || (typeof data.wide === "function" && data.wide.call(this));
- else if ( page_data.hasOwnProperty('wide') )
- is_wide = page_data.wide || (typeof page_data.wide === "function" && page_data.wide.call(this));
-
- var is_dash = is_wide && jQuery(menu).parents('.chat-container').hasClass('dash-chat');
- inner.style.maxWidth = is_wide ? (is_dash ? 380 : (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600)) + "px" : "";
-
- // Re-position if necessary.
- var f = this;
- setTimeout(function(){f._fix_menu_position();});
-}
-
-
-FFZ.prototype._ui_change_page = function(view, inner, menu, container, page) {
- this._last_page = page;
- container.innerHTML = "";
- container.setAttribute('data-page', page);
-
- // Get the data structure for the new page.
- var data = FFZ.menu_pages[page];
-
- // See if we're dealing with a sub-menu situation.
- if ( data.pages ) {
- // We need to render the sub-menu, and then call the _ui_change_sub_page method
- // to render the sub-page.
- var submenu = document.createElement('ul'),
- subcontainer = document.createElement('div'),
-
- height = parseInt(container.style.maxHeight || '0');
-
- if ( ! height )
- height = Math.max(200, view.$().height() - 172);
-
- if ( height && ! Number.isNaN(height) ) {
- height -= 37;
- subcontainer.style.maxHeight = height + 'px';
- }
-
- submenu.className = 'menu sub-menu clearfix';
- subcontainer.className = 'ffz-ui-sub-menu-page';
-
- // Building Tabs
- var subpages = [];
- for(var key in data.pages) {
- var subpage = data.pages[key];
- try {
- if ( ! subpage || (subpage.hasOwnProperty("visible") && (!subpage.visible || (typeof subpage.visible == "function" && !subpage.visible.call(this, view)))) )
- continue;
- } catch(err) {
- this.error("menu_pages " + page + " subpage " + key + " visible: " + err);
- continue;
- }
-
- subpages.push([subpage.sort_order || 0, key, subpage]);
- }
-
- subpages.sort(function(a,b) {
- if ( a[0] < b[0] ) return -1;
- else if ( a[0] > b[0] ) return 1;
-
- var al = a[1].toLowerCase(),
- bl = b[1].toLowerCase();
-
- if ( al < bl ) return -1;
- if ( al > bl ) return 1;
- return 0;
- });
-
- for(var i=0; i < subpages.length; i++) {
- var key = subpages[i][1],
- subpage = subpages[i][2],
- tab = document.createElement('li'),
- link = document.createElement('a');
-
- tab.className = 'item';
- tab.id = 'ffz-menu-page-' + page + '-subpage-' + key;
- link.innerHTML = subpage.name;
- link.addEventListener('click', this._ui_change_subpage.bind(this, view, inner, submenu, subcontainer, key));
-
- tab.appendChild(link);
- submenu.appendChild(tab);
- }
-
- // Add this to the container.
- container.appendChild(subcontainer);
- container.appendChild(submenu);
-
- // Activate a Tab
- var last_subpages = this._last_subpage = this._last_subpage || {},
- last_subpage = last_subpages[page] = last_subpages[page] || data.default_page || subpages[0][1];
-
- if ( typeof last_subpage === "function" )
- last_subpage = last_subpage.call(this);
-
- this._ui_change_subpage(view, inner, submenu, subcontainer, last_subpage);
-
- // Make sure the correct menu tab is selected
- jQuery('li.active', menu).removeClass('active');
- jQuery('#ffz-menu-page-' + page, menu).addClass('active');
-
- return;
- }
-
- // Render the page first. If there's an error, it won't update the other UI stuff.
- data.render.call(this, view, container, inner, menu);
-
- // Make sure the correct menu tab is selected
- jQuery('li.active', menu).removeClass('active');
- jQuery('#ffz-menu-page-' + page, menu).addClass('active');
-
- // Apply wideness - TODO: Revamp wide menus entirely for thin containers
- var is_wide = data.wide || (typeof data.wide === "function" && data.wide.call(this)),
- app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container");
-
- inner.style.maxWidth = is_wide ? (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600) + "px" : "";
-
- // Re-position if necessary.
- var f = this;
- setTimeout(function(){f._fix_menu_position();});
-}
-
-
-// --------------------
-// Channel Page
-// --------------------
-
-FFZ.menu_pages.channel = {
- render: function(view, inner) {
- // Get the current room.
- var controller = utils.ember_lookup('controller:chat'),
- room_id = controller.get('currentRoom.id'),
- room = this.rooms[room_id],
- has_product = false,
- f = this;
-
- // Check for a product.
- if ( this.settings.replace_twitch_menu ) {
- var product = room.room.get("product");
- if ( product && !product.get("error") ) {
- // We have a product, and no error~!
- has_product = true;
- var Ticket = utils.ember_resolve('model:ticket'),
- tickets = Ticket && Ticket.find('user', {channel: room_id}),
- subbed_products = _.pluck(tickets && tickets.get('content') || [], 'product'),
- subbed_plans = _.pluck(subbed_products, 'short_name'),
- subbed_emote_sets = _.uniq(_.flatten(_.map(subbed_products, function(x) { return x.features.emoticon_set_ids }))),
-
- is_subscribed = subbed_plans.length > 0,
- is_loaded = tickets ? tickets.get('isLoaded') : false,
- icon,
-
- grid = document.createElement("div"),
- header = document.createElement("div"),
- c = 0;
-
- // See if we've loaded. If we haven't loaded the ticket yet
- // then try loading it, and then re-render the menu.
- if ( tickets && ! is_subscribed && ! is_loaded ) {
- tickets.addObserver('isLoaded', function() {
- setTimeout(function(){
- if ( inner.getAttribute('data-page') !== 'channel' )
- return;
-
- inner.innerHTML = '';
- FFZ.menu_pages.channel.render.bind(f)(view, inner);
- },0);
-
- });
-
- tickets.load();
- }
-
- // Null-conditional try/catch
- try {
- icon = room.badges.subscriber.versions[0].image_url_1x;
- } catch(err) { }
-
- grid.className = "emoticon-grid top-set";
- header.className = "heading";
- if ( icon ) {
- header.style.backgroundImage = 'url("' + icon + '")';
- if ( icon.indexOf('.svg') !== -1 )
- header.style.backgroundSize = '18px';
- }
-
- header.innerHTML = 'Twitch Subscriber Emoticons';
- grid.appendChild(header);
-
- var all_emotes = {},
- plans = product.get("plans") || [],
- pwe = 0;
-
- if ( ! plans.length ) {
- // If we have a product with no defined plans, fake a plan with the required
- // information to keep our script happy.
- plans.push({
- name: product.name,
- product_url: product.product_url,
- price: product.price,
-
- emoticon_set_ids: _.uniq(_.pluck(product.emoticons, 'emoticon_set')),
- emoticons: product.emoticons
- })
- }
-
- for(var i=0; i < plans.length; i++) {
- var plan = plans[i],
- emotes = plan.emoticons || [],
- jm = emotes.length;
-
- if ( jm > 0 )
- pwe++;
-
- for(var j = 0; j < jm; j++) {
- var emote = emotes[j];
- if ( emote.state !== "active" )
- continue;
-
- var ae = all_emotes[emote.regex] = all_emotes[emote.regex] || [];
- ae.push([emote, i]);
- }
- }
-
- for(var ek in all_emotes) {
- var ems = all_emotes[ek];
- for(var i=0, l = ems.length; i < l; i++) {
- var emote = ems[i][0],
- plan_idx = ems[i][1],
- plan = plans[plan_idx],
- s = utils.createElement('span', 'emoticon ffz-tooltip ffz-tooltip-no-credit'),
-
- set_id = emote.emoticon_set,
- can_use = ! emote.subscriber_only || subbed_emote_sets.indexOf(set_id) !== -1,
- img_set = utils.build_srcset(emote.id);
-
- if ( ! can_use ) {
- s.classList.add('locked');
- s.dataset.sellout = 'Subscribe for ' + utils.sanitize(plan.price) + ' to unlock this emote. ';
- }
-
- if ( set_id ) {
- var faves = this.settings.favorite_emotes["twitch-" + set_id];
- s.classList.add('ffz-can-favorite');
- if ( faves && faves.indexOf(emote.id) !== -1 )
- s.classList.add('ffz-favorite');
- }
-
- s.dataset.plan = plan_idx;
- s.dataset.emote = emote.id;
- s.dataset.set = set_id;
- s.alt = emote.regex;
-
- s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
- s.style.backgroundImage = img_set;
-
- s.style.width = (10 + emote.width) + "px";
- s.style.height = (10 + emote.height) + "px";
-
- s.addEventListener('click', function(can_use, emote_id, code, set_id, plan_idx, e) {
- e.preventDefault();
- if ( ( e.shiftKey || e.shiftLeft) && this.settings.clickable_emoticons )
- window.open("https://twitchemotes.com/emote/" + emote_id);
- else if ( can_use )
- this._add_emote(view, code, "twitch-" + set_id, emote_id, e);
- else {
- var plan = plans[plan_idx],
- url = plan && plan.product_url;
- url && window.open(url);
- }
-
- }.bind(this, can_use, emote.id, emote.regex, set_id, plan_idx));
-
- grid.appendChild(s);
- c++;
- }
- }
-
- if ( c > 0 ) {
- inner.appendChild(grid);
- var msg;
-
- if ( ! is_loaded ) {
- msg = 'Loading sub information...';
-
- } else if ( ! is_subscribed && plans.length ) {
- var sub_message = utils.createElement('div', null, 'Subscribe to unlock ' + (pwe === 1 ? utils.number_commas(c) : 'some') + ' Sub Emotes'),
- sub_container = utils.createElement('div', 'mg-l-1 mg-r-1 align-center', sub_message),
- had_prime = false,
-
- filter_grid = function(price, sets) {
- var kids = grid.querySelectorAll('.emoticon'),
- count = 0;
- for(var i=0, l = kids.length; i < l; i++) {
- var emote = kids[i],
- set_id = parseInt(emote.dataset.set),
- would_unlock = sets ? sets.indexOf(set_id) !== -1 : false;
-
- if ( would_unlock )
- count++;
-
- emote.classList.toggle('unlocked', would_unlock);
- }
-
- sub_message.textContent = price ?
- 'Subscribe for ' + price +
- ' to unlock ' + utils.number_commas(count) + ' Sub Emotes'
- : sub_message.dataset.original;
- };
-
- sub_message.dataset.original = sub_message.textContent;
-
- for(var i=0; i < plans.length; i++) {
- var plan = plans[i],
- btn = utils.createElement('a', 'ffz-sub-button button mg-t-1', utils.sanitize(plan.price));
-
- sub_container.appendChild(btn);
-
- btn.href = plan.product_url;
- btn.target = '_blank';
- btn.rel = 'noopener noreferrer';
-
- btn.addEventListener('mouseover', filter_grid.bind(this, plan.price, plan.emoticon_set_ids));
- btn.addEventListener('mouseout', filter_grid.bind(this, null, null));
- }
-
- inner.appendChild(sub_container);
-
- } else if ( plans.length ) {
- // We are subscribed. Check to see if the subscription will expire.
- var content = tickets.get('content.lastObject'),
- pp = content && content.purchase_profile,
- ends_at = content && utils.parse_date(content.access_end);
-
- if ( pp && ends_at ) {
- var now = Date.now() - (this._ws_server_offset || 0),
- end_time = ends_at ? Math.floor((ends_at.getTime() - now) / 1000) : null,
-
- provider = pp.payment_provider,
- renews = pp.will_renew;
-
- msg = 'Subscription ' + (renews ? 'renews' : 'expires') +
- ' in ' + utils.time_to_string(end_time, true, true);
- }
- }
-
- if ( msg ) {
- var sub_message = utils.createElement('div', null, msg),
- sub_container = utils.createElement('div', 'mg-l-1 mg-r-1 align-center', sub_message);
-
- inner.appendChild(sub_container);
- }
- }
- }
- }
-
- // Do we have extra sets?
- var extra_sets = _.union(room && room.extra_sets || [], room && room.ext_sets || [], []);
-
- // Basic Emote Sets
- this._emotes_for_set(inner, view, room && room.set, (this.feature_friday || has_product || extra_sets.length ) ? "Channel Emoticons" : null, (room && room.moderator_badge) || "//cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ", ! has_product && extra_sets.length);
-
- for(var i=0; i < extra_sets.length; i++) {
- // Look up the set name.
- var set = this.emote_sets[extra_sets[i]],
- name = set ? (set.hasOwnProperty('source_ext') ? "" : "Featured ") + set.title : "Featured Channel";
-
- if ( ! set || ! set.count || set.hidden )
- continue;
-
- this._emotes_for_set(inner, view, extra_sets[i], name, set.icon || "//cdn.frankerfacez.com/script/devicon.png", set.source || "FrankerFaceZ");
- }
-
- // Feature Friday!
- this._feature_friday_ui(room_id, inner, view);
- },
-
- name: "Channel",
- icon: constants.ZREKNARF
- };
-
-
-// --------------------
-// Emotes for Sets
-// --------------------
-
-FFZ.prototype._emotes_for_set = function(parent, view, set_id, header, image, sub_text, top_set) {
- var grid = document.createElement('div'), c = 0, f = this,
- set = this.emote_sets[set_id];
-
- grid.className = 'emoticon-grid';
- if ( top_set )
- grid.classList.add('top-set');
-
- if ( header != null ) {
- var el_header = document.createElement('div');
- el_header.className = 'heading';
-
- if ( sub_text ) {
- var s = document.createElement("span");
- s.className = "right";
- s.appendChild(document.createTextNode(sub_text));
- el_header.appendChild(s);
- }
-
- el_header.appendChild(document.createTextNode(header));
-
- if ( image ) {
- el_header.style.backgroundImage = 'url("' + image + '")';
- if ( image.indexOf('.svg') !== -1 )
- el_header.style.backgroundSize = '18px';
- }
-
- grid.appendChild(el_header);
- }
-
- var emotes = [];
- if ( set && set.emoticons )
- for(var eid in set.emoticons) {
- if ( ! set.emoticons.hasOwnProperty(eid) || set.emoticons[eid].hidden )
- continue;
-
- emotes.push(set.emoticons[eid]);
- }
-
- // Sort the emotes!
- emotes.sort(function(a,b) {
- var an = a.name.toLowerCase(),
- bn = b.name.toLowerCase();
-
- if ( an < bn ) return -1;
- else if ( an > bn ) return 1;
- return 0;
- });
-
- // Favoriting Info
- var favorite_key = set ? 'ffz-' + (set.hasOwnProperty('source_ext') ? 'ext-' + set.source_ext + '-' + set.source_id : set.id) : undefined,
- favorites = this.settings.favorite_emotes[favorite_key] || [];
-
- for(var i=0; i < emotes.length; i++) {
- var emote = emotes[i], srcset = null;
-
- if ( emote.urls[2] || emote.urls[4] ) {
- srcset = 'url("' + emote.urls[1] + '") 1x';
- if ( emote.urls[2] )
- srcset += ', url("' + emote.urls[2] + '") 2x';
- if ( emote.urls[4] )
- srcset += ', url("' + emote.urls[4] + '") 4x';
- }
-
- c++;
- var s = document.createElement('span');
- s.className = 'emoticon ffz-tooltip ffz-can-favorite';
- if ( favorites.indexOf(emote.id) !== -1 )
- s.classList.add('ffz-favorite');
-
- s.setAttribute('data-ffz-emote', emote.id);
- s.setAttribute('data-ffz-set', set.id);
-
- s.style.backgroundImage = 'url("' + emote.urls[1] + '")';
-
- if ( srcset ) {
- var img_set = 'image-set(' + srcset + ')';
- s.style.backgroundImage = '-webkit-' + img_set;
- s.style.backgroundImage = '-moz-' + img_set;
- s.style.backgroundImage = '-ms-' + img_set;
- s.style.backgroundImage = img_set;
- }
-
- s.style.width = (10+emote.width) + "px";
- s.style.height = (10+emote.height) + "px";
-
- s.addEventListener('click', function(id, code, e) {
- e.preventDefault();
- if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) {
- var url;
- if ( set.hasOwnProperty('source_ext') ) {
- var api = f._apis[set.source_ext];
- if ( api && api.emote_url_generator )
- url = api.emote_url_generator(set.source_id, id);
- } else
- url = "https://www.frankerfacez.com/emoticon/" + id;
- if ( url )
- window.open(url);
- } else
- this._add_emote(view, code, favorite_key, id, e);
- }.bind(this, emote.id, emote.name));
-
- grid.appendChild(s);
- }
-
- if ( !c ) {
- grid.innerHTML += "This channel has no emoticons.";
- grid.className = "emoticon-grid ffz-no-emotes center";
- }
-
- parent.appendChild(grid);
-}
-
-
-FFZ.prototype._add_emote = function(view, emote, favorites_set, favorites_key, event) {
- if ( event && ((!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey)) ) {
- var el = event.target;
- if ( ! el.classList.contains('locked') && el.classList.contains('ffz-can-favorite') && favorites_set && favorites_key ) {
- var favs = this.settings.favorite_emotes[favorites_set] = this.settings.favorite_emotes[favorites_set] || [],
- is_favorited = favs.indexOf(favorites_key) !== -1;
-
- if ( is_favorited )
- favs.removeObject(favorites_key);
- else
- favs.push(favorites_key);
-
- this.settings.set("favorite_emotes", this.settings.favorite_emotes, true);
- this._inputv && this._inputv.propertyDidChange('ffz_emoticons');
-
- if ( el.classList.contains('ffz-is-favorite') && is_favorited ) {
- jQuery(el).trigger('mouseout');
- el.parentElement.removeChild(el);
- } else
- el.classList.toggle('ffz-favorite', ! is_favorited);
- }
- return;
- }
-
- var input_el, text, room;
-
- if ( this.has_bttv_6 ) {
- input_el = view.get('element').querySelector('textarea');
- text = input_el.value;
-
- } else {
- var controller = utils.ember_lookup('controller:chat');
- room = controller.get('currentRoom');
- text = room.get('messageToSend') || '';
- }
-
- text += (text && text.substr(-1) !== " " ? " " : "") + (emote.name || emote);
-
- if ( input_el )
- input_el.value = text;
- else
- room.set('messageToSend', text);
-}
\ No newline at end of file
diff --git a/src/ui/menu_button.js b/src/ui/menu_button.js
deleted file mode 100644
index 581ce0c5..00000000
--- a/src/ui/menu_button.js
+++ /dev/null
@@ -1,46 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils');
-
-// --------------------
-// Initialization
-// --------------------
-
-FFZ.prototype.build_ui_link = function(view) {
- var link = document.createElement('a');
- link.className = 'ffz-ui-toggle';
- link.innerHTML = constants.CHAT_BUTTON;
-
- link.addEventListener('click', this.build_ui_popup.bind(this, view));
-
- this.update_ui_link(link);
- return link;
-}
-
-
-FFZ.prototype.update_ui_link = function(link) {
- var controller = utils.ember_lookup('controller:chat');
- link = link || document.querySelector('a.ffz-ui-toggle');
- if ( !link || !controller )
- return;
-
- var room_id = controller.get('currentRoom.id'),
- room = this.rooms[room_id],
- has_emotes = false,
-
- blue = (this.has_bttv_6 ? BetterTTV.settings.get('showBlueButtons') : false),
- live = (this.feature_friday && this.feature_friday.live);
-
-
- // Check for emoticons.
- if ( room && room.set ) {
- var set = this.emote_sets[room.set];
- if ( set && set.count > 0 )
- has_emotes = true;
- }
-
- link.classList.toggle('no-emotes', ! has_emotes);
- link.classList.toggle('live', live);
- link.classList.toggle('blue', blue);
- //link.classList.toggle('news', this._has_news);
-}
\ No newline at end of file
diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js
deleted file mode 100644
index a9f59f59..00000000
--- a/src/ui/my_emotes.js
+++ /dev/null
@@ -1,807 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require("../constants"),
- utils = require("../utils"),
-
- BANNED_SETS = {"00000turbo":true},
- EXTRA_INVENTORY = ['33563'];
-
-
-// -------------------
-// Initialization
-// -------------------
-
-FFZ.basic_settings.replace_twitch_menu = {
- type: "boolean",
-
- category: "Chat",
-
- name: "Unified Emoticons Menu",
- help: "Completely replace the default Twitch emoticon menu and display global emoticons in the My Emoticons menu.",
-
- get: function() {
- return this.settings.replace_twitch_menu && this.settings.global_emotes_in_menu && this.settings.emoji_in_menu;
- },
-
- set: function(val) {
- this.settings.set('replace_twitch_menu', val);
- this.settings.set('global_emotes_in_menu', val);
- this.settings.set('emoji_in_menu', val);
- }
-};
-
-FFZ.settings_info.replace_twitch_menu = {
- type: "boolean",
- value: false,
-
- category: "Chat Input",
-
- name: "Replace Twitch Emoticon Menu",
- help: "Completely replace the default Twitch emoticon menu.",
-
- on_update: function(val) {
- document.body.classList.toggle("ffz-menu-replace", val);
- }
- };
-
-
-FFZ.settings_info.global_emotes_in_menu = {
- type: "boolean",
- value: false,
-
- category: "Chat Input",
-
- name: "Display Global Emotes in My Emotes",
- help: "Display the global Twitch emotes in the My Emoticons menu."
- };
-
-
-FFZ.settings_info.emoji_in_menu = {
- type: "boolean",
- value: true,
-
- category: "Chat Input",
-
- name: "Display Emoji in My Emotes",
- help: "Display the supported emoji images in the My Emoticons menu."
- };
-
-
-FFZ.settings_info.emote_menu_collapsed = {
- storage_key: "ffz_setting_my_emoticons_collapsed_sections",
- value: [],
- visible: false
-}
-
-
-FFZ.settings_info.favorite_emotes = {
- value: {},
- visible: false
-}
-
-
-FFZ.prototype.setup_my_emotes = function() {
- var UserEmotes = utils.ember_lookup('service:user-emotes');
- if ( UserEmotes ) {
- this.modify_user_emotes(UserEmotes);
- UserEmotes.ffzUpdateData();
- }
-
- this._twitch_badges = {};
- this._twitch_badges["--inventory--"] = "//cdn.frankerfacez.com/script/inventory_icon.svg";
- this._twitch_badges["--global--"] = "//cdn.frankerfacez.com/script/twitch_logo.png";
- this._twitch_badges["--turbo-faces--"] = this._twitch_badges["turbo"] = "//cdn.frankerfacez.com/script/turbo_badge.png";
- this._twitch_badges["--prime-faces--"] = this._twitch_badges["--prime--"] = "//cdn.frankerfacez.com/badges/twitch/premium/1/1.png";
- this._twitch_badges["--curse--"] = "//cdn.frankerfacez.com/script/curse_logo.png";
-}
-
-
-FFZ.prototype.modify_user_emotes = function(service) {
- var f = this;
- service.reopen({
- ffzUpdateData: function() {
- var emotes = (this.get('allEmotes') || {})['emoticon_sets'] || {};
- for(var set_id in emotes) {
- f.get_twitch_set(set_id);
- var es = emotes[set_id] || [],
- esl = es.length,
- sid = typeof set_id === "number" ? set_id : parseInt(set_id);
-
- for(var i=0; i < esl; i++)
- f._twitch_emote_to_set[es[i].id] = sid;
- }
-
- if ( f._inputv )
- Ember.propertyDidChange(f._inputv, 'ffz_emoticons');
-
- }.observes('allEmotes')
- });
-}
-
-
-// -------------------
-// Menu Page
-// -------------------
-
-FFZ.menu_pages.myemotes = {
- name: "My Emoticons",
- icon: constants.EMOTE,
-
- has_sets: function(view) {
- var user = this.get_user(),
- controller = utils.ember_lookup('controller:chat'),
- user_emotes = utils.ember_lookup('service:user-emotes'),
- twitch_sets = (user_emotes && user_emotes.allEmotes || {})['emoticon_sets'] || {},
- ffz_sets = user && this.users[user.login] && this.users[user.login].sets || [],
-
- 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);
- },
-
- visible: function(view) {
- return this.settings.emoji_in_menu || FFZ.menu_pages.myemotes.has_sets.call(this, view);
- },
-
- default_page: function() {
- for(var key in this.settings.favorite_emotes)
- if ( this.settings.favorite_emotes[key] && this.settings.favorite_emotes[key].length )
- return 'favorites';
-
- var has_emotes = FFZ.menu_pages.myemotes.has_sets.call(this);
- if ( has_emotes )
- return 'all';
-
- if ( this.settings.emoji_in_menu )
- return 'emoji';
-
- return 'favorites';
- },
-
- pages: {
- favorites: {
- name: "Favorites",
- sort_order: 1,
-
- render: function(view, container) {
- FFZ.menu_pages.myemotes.render_lists.call(this, view, container, true);
-
- var el = document.createElement("div");
- el.className = "emoticon-grid ffz-no-emotes center";
- el.innerHTML = "You have no favorite emoticons. To make an emote a favorite, find it and " + (constants.IS_OSX ? '⌘' : 'Ctrl') + "-Click it.";
- container.appendChild(el);
- }
- },
-
- all: {
- name: "All Emoticons",
- sort_order: 2,
-
- visible: function(view) {
- return FFZ.menu_pages.myemotes.has_sets.call(this, view);
- },
-
- render: function(view, container) {
- /*var search_cont = utils.createElement('div', 'ffz-filter-container'),
- search_input = utils.createElement('input', 'emoticon-selector__filter-input form__input js-filter-input text text--full-width'),
- filtered_cont = utils.createElement('div', 'ffz-filter-children ffz-ui-sub-menu-page'),
- was_filtered = false;
-
- search_input.placeholder = 'Search for Emotes';
- search_input.type = 'text';
-
- filtered_cont.style.maxHeight = (parseInt(container.style.maxHeight) - 53) + 'px';
-
- search_cont.appendChild(search_input);
- container.appendChild(filtered_cont);
- container.appendChild(search_cont);
-
- search_input.addEventListener('input', function(e) {
- var filter = (search_input.value || '').toLowerCase(),
- groups = filtered_cont.querySelectorAll('.emoticon-grid');
-
- for(var i=0; i < groups.length; i++) {
- var el = groups[i],
- emotes = el.querySelectorAll('.emoticon'),
- hidden = true;
-
- for(var j=0; j < emotes.length; j++) {
- var em = emotes[j],
- ehidden = filter.length && em.getAttribute('data-filter').indexOf(filter) === -1;
-
- em.classList.toggle('hidden', ehidden);
- hidden = hidden && ehidden;
- }
-
- el.classList.toggle('hidden', hidden);
- el.classList.toggle('collapsable', ! filter.length);
- }
- });
-
- container = filtered_cont;*/
- FFZ.menu_pages.myemotes.render_lists.call(this, view, container, false);
- }
- },
-
- emoji: {
- name: "Emoji",
- sort_order: 3,
- visible: function() { return this.settings.emoji_in_menu },
-
- render: function(view, container) {
- var sets = [];
-
- for(var cat in constants.EMOJI_CATEGORIES) {
- var menu = FFZ.menu_pages.myemotes.draw_emoji.call(this, view, cat, false);
- if ( menu )
- sets.push([cat, menu]);
- }
-
- sets.sort(function(a,b) {
- var an = a[0], bn = b[0];
- if ( an < bn ) return -1;
- if ( an > bn ) return 1;
- return 0;
- });
-
- if ( sets.length )
- sets[0][1].classList.add('top-set');
-
- for(var i=0; i < sets.length; i++)
- container.appendChild(sets[i][1]);
- }
- }
- },
-
- render_lists: function(view, container, favorites_only) {
- var controller = utils.ember_lookup('controller:chat'),
- user_emotes = utils.ember_lookup('service:user-emotes'),
- twitch_sets = (user_emotes && user_emotes.allEmotes || {})['emoticon_sets'] || {},
-
- user = this.get_user(),
- ffz_sets = this.getEmotes(user && user.login, null),
- sets = [],
- gathered_emotes = [];
-
- // Start with Twitch Sets
- var gathered_favorites = this.settings.favorite_emotes['twitch-inventory'] || [],
- gathered_channels = {},
- other_channels = [];
-
- for(var set_id in twitch_sets) {
- if ( ! twitch_sets.hasOwnProperty(set_id) || ( ! favorites_only && ! this.settings.global_emotes_in_menu && set_id === '0' ) )
- continue;
-
- // Skip the Twitch Turbo set if we have Twitch Prime. They're identical.
- if ( set_id == 793 && twitch_sets.hasOwnProperty(19194) )
- continue;
-
- var set = twitch_sets[set_id];
- if ( ! set.length )
- continue;
-
- if ( this._twitch_inventory_sets.indexOf(set_id) !== -1 || EXTRA_INVENTORY.indexOf(set_id) !== -1 ) {
- for(var i=0; i < set.length; i++)
- if ( ! favorites_only || gathered_favorites.indexOf(set[i].id) !== -1 )
- gathered_emotes.push(set[i]);
-
- continue;
- }
-
- var raw_data = this.get_twitch_set(set_id),
- raw_id = raw_data && raw_data.c_name,
- menu_id = raw_id ? raw_id.toLowerCase() : 'unknown',
- favorites_list = this.settings.favorite_emotes["twitch-" + set_id];
-
- if ( favorites_only && (! favorites_list || ! favorites_list.length) )
- continue;
-
- if ( menu_id !== 'unknown' ) {
- var gathered = gathered_channels[menu_id] = gathered_channels[menu_id] || [];
- gathered.push(set_id);
- continue;
- }
-
- var sort_key = 0,
- menu = FFZ.menu_pages.myemotes.draw_twitch_set.call(this, view, set_id, set, favorites_only);
-
- if ( menu_id.indexOf('global') !== -1 )
- sort_key = 100;
- else if ( menu_id.substr(0,2) === '--' || menu_id === 'turbo' )
- sort_key = 75;
-
- if ( menu )
- sets.push([[sort_key, menu_id], menu]);
- }
-
- for(var menu_id in gathered_channels) {
- var gathered = [],
- stuff = gathered_channels[menu_id];
-
- if ( ! stuff.length )
- continue;
-
- for(var i=0; i < stuff.length; i++) {
- var set_id = stuff[i],
- set = twitch_sets[set_id];
- for(var j=0; j < set.length; j++)
- gathered.push([set_id, set[j]]);
- }
-
- var sort_key = 0,
- menu = FFZ.menu_pages.myemotes.draw_twitch_set.call(this, view, stuff[0], gathered, favorites_only);
-
- if ( menu_id.indexOf('global') !== -1 )
- sort_key = 100;
- else if ( menu_id.substr(0,2) === '--' || menu_id === 'turbo' )
- sort_key = 75;
-
- if ( menu )
- sets.push([[sort_key, menu_id], menu]);
- }
-
-
- // Handle the gathered single emotes.
- if ( gathered_emotes.length ) {
- var menu = FFZ.menu_pages.myemotes.draw_twitch_set.call(this, view, 'inventory', gathered_emotes, favorites_only);
- sets.push([[50, 'twitch-inventory'], menu]);
- }
-
-
- // Emoji~!
- if ( favorites_only && this.settings.emoji_in_menu ) {
- var favorites_list = this.settings.favorite_emotes["emoji"];
- if ( favorites_list && favorites_list.length ) {
- var menu = FFZ.menu_pages.myemotes.draw_emoji.call(this, view, null, favorites_only);
- if ( menu )
- sets.push([[200, "emoji"], menu]);
- }
- }
-
- // Now, FFZ!
- if ( favorites_only ) {
- // But first, inject all the sets from this specific room.
- var ffz_room = controller && this.rooms && this.rooms[controller.get('currentRoom.id')];
- if ( ffz_room ) {
- if ( ffz_room.set && ffz_sets.indexOf(ffz_room.set) === -1 )
- ffz_sets.push(ffz_room.set);
-
- if ( ffz_room.sets && ffz_room.sets.length )
- ffz_sets = _.uniq(ffz_sets.concat(ffz_room.sets));
-
- if ( ffz_room.extra_sets && ffz_room.extra_sets.length )
- ffz_sets = _.uniq(ffz_sets.concat(ffz_room.extra_sets));
- }
- }
-
- for(var i=0; i < ffz_sets.length; i++) {
- var set_id = ffz_sets[i],
- set = this.emote_sets[set_id];
- if ( ! set || ! set.count || set.hidden || ( ! favorites_only && ! this.settings.global_emotes_in_menu && this.default_sets.indexOf(set_id) !== -1 ) )
- continue;
-
- var menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id,
- favorites_list = this.settings.favorite_emotes[menu_id];
-
- if ( favorites_only && (! favorites_list || ! favorites_list.length) )
- continue;
-
- var menu_id = set.title.toLowerCase(),
- sort_key = set.sort,
- menu = FFZ.menu_pages.myemotes.draw_ffz_set.call(this, view, set, favorites_only);
-
- if ( sort_key === undefined || sort_key === null ) {
- if ( menu_id.indexOf('global') !== -1 )
- sort_key = 100;
- else
- sort_key = 0;
- }
-
- if ( menu )
- sets.push([[sort_key, menu_id], menu]);
- }
-
-
- if ( ! sets.length )
- return false;
-
-
- // Finally, sort and add them all.
- sets.sort(function(a,b) {
- var ask = a[0][0],
- an = a[0][1],
-
- bsk = b[0][0],
- bn = b[0][1];
-
- if ( ask < bsk ) return -1;
- if ( ask > bsk ) return 1;
-
- if ( an < bn ) return -1;
- if ( an > bn ) return 1;
- return 0;
- });
-
- if ( favorites_only ) {
- var grid = document.createElement('div');
- grid.className = 'emoticon-grid favorites-grid';
- for(var i=0; i < sets.length; i++)
- grid.appendChild(sets[i][1]);
-
- container.appendChild(grid);
-
- } else if ( sets.length ) {
- sets[0][1].classList.add('top-set');
- for(var i=0; i < sets.length; i++)
- container.appendChild(sets[i][1]);
- }
-
- return true;
- },
-
- toggle_section: function(heading, container, set_state) {
- var menu = heading.parentElement,
- set_id = menu.getAttribute('data-set'),
- collapsed_list = this.settings.emote_menu_collapsed,
- has_state = set_state !== undefined,
- is_collapsed = has_state ? set_state : collapsed_list.indexOf(set_id) === -1;
-
- if ( ! has_state ) {
- if ( ! is_collapsed )
- collapsed_list.removeObject(set_id);
- else
- collapsed_list.push(set_id);
-
- this.settings.set('emote_menu_collapsed', collapsed_list, true);
- }
-
- menu.classList.toggle('collapsed', !is_collapsed);
-
- if ( is_collapsed )
- menu.appendChild(container);
- else
- menu.removeChild(container);
- },
-
- draw_emoji: function(view, cat, favorites_only) {
- var heading = document.createElement('div'),
- menu = document.createElement('div'),
- menu_id = 'emoji' + (cat ? '-' + cat : ''),
- emotes = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
- collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf(menu_id) === -1,
- f = this,
- settings = this.settings.parse_emoji || 1,
-
- favorites = this.settings.favorite_emotes["emoji"] || [],
- c = 0;
-
- menu.className = 'emoticon-grid';
- menu.setAttribute('data-set', menu_id);
-
- if ( ! favorites_only ) {
- heading.className = 'heading';
- heading.innerHTML = 'Unicode ' + (cat ? utils.sanitize(constants.EMOJI_CATEGORIES[cat]) : 'Emoji');
- heading.style.backgroundImage = 'url("' + constants.SERVER + 'emoji/' + (settings === 3 ? 'one/' : (settings === 2 ? 'noto-' : 'tw/')) + (constants.EMOJI_LOGOS[cat] || '1f4af') + '.svg")';
- heading.style.backgroundSize = "18px";
-
- menu.classList.add('collapsable');
- menu.appendChild(heading);
- menu.classList.toggle('collapsed', collapsed);
- heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emotes); });
- }
-
- var set = [];
-
- for(var eid in this.emoji_data)
- set.push(this.emoji_data[eid]);
-
- set.sort(function(a,b) {
- var an = (a.name || "").toLowerCase(),
- bn = (b.name || "").toLowerCase();
-
- if ( an < bn ) return -1;
- else if ( an > bn ) return 1;
- if ( a.raw < b.raw ) return -1;
- if ( a.raw > b.raw ) return 1;
- return 0;
- });
-
- for(var i=0; i < set.length; i++) {
- var emoji = set[i],
- em = document.createElement('span');
-
- if ( (cat && cat !== emoji.cat) || (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) || (settings === 3 && ! emoji.one) )
- continue;
-
- var is_favorite = favorites.indexOf(emoji.raw) !== -1,
- src = settings === 3 ? emoji.one_src : (settings === 2 ? emoji.noto_src : emoji.tw_src),
- image = this.settings.emote_image_hover ? ' ' : '';
-
- if ( favorites_only && ! is_favorite )
- continue;
-
- em.className = 'emoticon emoji ffz-tooltip ffz-can-favorite';
- em.classList.toggle('ffz-favorite', is_favorite);
- em.classList.toggle('ffz-is-favorite', favorites_only);
-
- em.setAttribute('data-ffz-emoji', emoji.code);
- em.setAttribute('data-filter', emoji.name.toLowerCase() + ' :' + emoji.short_name.toLowerCase() + ':');
- em.alt = emoji.raw;
-
- em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw, "emoji", emoji.raw));
-
- em.style.backgroundImage = 'url("' + src + '")';
- em.style.backgroundSize = "18px";
-
- c++;
- emotes.appendChild(em);
- }
-
- if ( ! c )
- return;
-
- if ( favorites_only )
- return emotes;
-
- if ( ! collapsed )
- menu.appendChild(emotes);
-
- return menu;
- },
-
- draw_twitch_set: function(view, set_id, set, favorites_only) {
- var heading = document.createElement('div'),
- menu = document.createElement('div'),
- emotes = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
- collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) === -1,
- f = this,
-
- set_data = this.get_twitch_set(set_id),
- channel_id = set_id === 'inventory' ? '--inventory--' : (set_data && set_data.c_name || 'twitch_unknown'), title,
- favorites = this.settings.favorite_emotes["twitch-" + set_id] || [],
- c = 0;
-
- menu.className = 'emoticon-grid';
- menu.setAttribute('data-set', 'twitch-' + set_id);
-
- if ( ! favorites_only ) {
- if ( channel_id === '--inventory--' )
- title = "Inventory";
- else if ( channel_id === "twitch_unknown" )
- title = "Unknown Channel";
- else if ( channel_id === "--global--" )
- title = "Global Emoticons";
- else if ( channel_id === "turbo" || channel_id === "--turbo-faces--" )
- title = "Twitch Turbo";
- else if ( channel_id === "--prime--" || channel_id === "--prime-faces--" )
- title = "Twitch Prime";
- else if ( channel_id === "--curse--" )
- title = "Curse Emoticons";
- else
- title = FFZ.get_capitalization(channel_id, function(name) {
- heading.innerHTML = 'Twitch ' + utils.sanitize(name);
- });
-
- heading.className = 'heading';
- heading.innerHTML = 'Twitch ' + utils.sanitize(title);
-
- var icon = this._twitch_badges[channel_id];
- if ( icon ) {
- heading.style.backgroundImage = 'url("' + icon + '")';
- if ( icon.indexOf('.svg') !== -1 )
- heading.style.backgroundSize = "18px";
- } else if ( set_data ) {
- var f = this;
- fetch("https://badges.twitch.tv/v1/badges/channels/" + set_data.c_id + "/display?language=" + (Twitch.receivedLanguage || "en"), {
- headers: {
- 'Client-ID': constants.CLIENT_ID
- }
- }).then(utils.json).then(function(data) {
- try {
- var badge = f._twitch_badges[channel_id] = data.badge_sets.subscriber.versions[0].image_url_1x;
- heading.style.backgroundImage = 'url("' + badge + '")';
- } catch(err) { /* Lament JS's lack of null coalescing operators */ }
- });
- }
-
- menu.classList.add('collapsable');
- menu.appendChild(heading);
- menu.classList.toggle('collapsed', collapsed);
- heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emotes); });
- }
-
- set.sort(function(a,b) {
- if ( Array.isArray(a) )
- a = a[1];
- if ( Array.isArray(b) )
- b = b[1];
-
- var an = a.code.toLowerCase(),
- bn = b.code.toLowerCase();
-
- if ( an < bn ) return -1;
- else if ( an > bn ) return 1;
- if ( a.id < b.id ) return -1;
- if ( a.id > b.id ) return 1;
- return 0;
- });
-
- for(var i=0; i < set.length; i++) {
- var emote = set[i],
- esid = set_id,
- favs = favorites;
- if ( Array.isArray(emote) ) {
- esid = emote[0];
- emote = emote[1];
- favs = this.settings.favorite_emotes["twitch-" + esid] || [];
- }
-
- var code = constants.KNOWN_CODES[emote.code] || emote.code,
- is_favorite = favs.indexOf(emote.id) !== -1;
-
- if ( favorites_only && ! is_favorite )
- continue;
-
- var em = document.createElement('span'),
- img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x)';
-
- em.className = 'emoticon ffz-tooltip ffz-can-favorite';
- em.setAttribute('data-emote', emote.id);
- em.setAttribute('data-filter', code.toLowerCase());
- em.alt = code;
-
- em.classList.toggle('ffz-favorite', is_favorite);
- em.classList.toggle('ffz-is-favorite', favorites_only);
- em.classList.toggle('ffz-tooltip-no-credit', ! favorites_only);
-
- if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) {
- em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")';
- } else {
- em.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
- em.style.backgroundImage = '-webkit-' + img_set;
- em.style.backgroundImage = '-moz-' + img_set;
- em.style.backgroundImage = '-ms-' + img_set;
- em.style.backgroundImage = img_set;
- }
-
- em.addEventListener("click", function(id, c, q, e) {
- e.preventDefault();
- if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
- window.open("https://twitchemotes.com/emote/" + id);
- else
- this._add_emote(view, c, "twitch-" + q, id, e);
- }.bind(this, emote.id, code, esid));
-
- c++;
- emotes.appendChild(em);
- }
-
- if ( ! c )
- return;
-
- if ( favorites_only )
- return emotes;
-
- if ( ! collapsed )
- menu.appendChild(emotes);
-
- return menu;
- },
-
- draw_ffz_set: function(view, set, favorites_only) {
- var heading = document.createElement('div'),
- menu = document.createElement('div'),
- emote_container = favorites_only ? document.createDocumentFragment() : document.createElement('div'),
- f = this,
- emotes = [],
-
- menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id,
- favorites = this.settings.favorite_emotes[menu_id] || [],
- c = 0,
- icon = set.icon || (set.hasOwnProperty('source_ext') && this._apis[set.source_ext] && this._apis[set.source_ext].icon) || '//cdn.frankerfacez.com/script/devicon.png',
- collapsed = ! favorites_only && this.settings.emote_menu_collapsed.indexOf(menu_id) === -1;
-
- menu.className = 'emoticon-grid';
- menu.setAttribute('data-set', menu_id);
-
- if ( ! favorites_only ) {
- menu.classList.add('collapsable');
-
- heading.className = 'heading';
- heading.innerHTML = '' + (utils.sanitize(set.source) || 'FrankerFaceZ') + ' ' + set.title;
-
- heading.style.backgroundImage = 'url("' + icon + '")';
- if ( icon.indexOf('.svg') !== -1 )
- heading.style.backgroundSize = "18px";
-
- menu.appendChild(heading);
- menu.classList.toggle('collapsed', collapsed);
- heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this, emote_container); });
- }
-
- for(var emote_id in set.emoticons)
- set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]);
-
- emotes.sort(function(a,b) {
- var an = a.name.toLowerCase(),
- bn = b.name.toLowerCase();
-
- if ( an < bn ) return -1;
- else if ( an > bn ) return 1;
- if ( a.id < b.id ) return -1;
- if ( a.id > b.id ) return 1;
- return 0;
- });
-
- for(var i=0; i < emotes.length; i++) {
- var emote = emotes[i],
- is_favorite = favorites.indexOf(emote.id) !== -1;
-
- if ( favorites_only && ! is_favorite )
- continue;
-
- var em = document.createElement('span'),
- img_set = 'image-set(url("' + emote.urls[1] + '") 1x';
-
- if ( emote.urls[2] )
- img_set += ', url("' + emote.urls[2] + '") 2x';
-
- if ( emote.urls[4] )
- img_set += ', url("' + emote.urls[4] + '") 4x';
-
- img_set += ')';
-
- em.className = 'emoticon ffz-tooltip ffz-can-favorite';
- em.classList.toggle('ffz-favorite', is_favorite);
- em.classList.toggle('ffz-is-favorite', favorites_only);
-
- em.setAttribute('data-ffz-emote', emote.id);
- em.setAttribute('data-ffz-set', set.id);
- em.setAttribute('data-filter', emote.name.toLowerCase());
-
- em.style.backgroundImage = 'url("' + emote.urls[1] + '")';
- em.style.backgroundImage = '-webkit-' + img_set;
- em.style.backgroundImage = '-moz-' + img_set;
- em.style.backgroundImage = '-ms-' + img_set;
- em.style.backgroundImage = img_set;
-
- if ( emote.height )
- em.style.height = (10+emote.height) + "px";
- if ( emote.width )
- em.style.width = (10+emote.width) + "px";
-
- em.addEventListener("click", function(id, code, e) {
- e.preventDefault();
- if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) {
- var url;
- if ( set.hasOwnProperty('source_ext') ) {
- var api = f._apis[set.source_ext];
- if ( api && api.emote_url_generator )
- url = api.emote_url_generator(set.source_id, id);
- } else
- url = "https://www.frankerfacez.com/emoticons/" + id;
-
- if ( url )
- window.open(url);
- } else
- this._add_emote(view, code, menu_id, id, e);
- }.bind(this, emote.id, emote.name));
-
- c++;
- emote_container.appendChild(em);
- }
-
- if ( ! c )
- return;
-
- if ( favorites_only )
- return emote_container;
-
- if ( ! collapsed )
- menu.appendChild(emote_container);
-
- return menu;
- }
-};
\ No newline at end of file
diff --git a/src/ui/notifications.js b/src/ui/notifications.js
deleted file mode 100644
index 68b9be38..00000000
--- a/src/ui/notifications.js
+++ /dev/null
@@ -1,206 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require("../utils"),
- constants = require("../constants");
-
-
-// ---------------------
-// Initialization
-// ---------------------
-
-FFZ.prototype.setup_notifications = function() {
- this.log("Adding event handler for window focus.");
- window.addEventListener("focus", this.clear_notifications.bind(this));
-}
-
-
-// ---------------------
-// Settings
-// ---------------------
-
-FFZ.settings_info.server_messages = {
- type: "boolean",
- value: true,
-
- category: "Appearance",
- name: "Server Notifications",
- help: "Display global FrankerFaceZ notifications."
- };
-
-
-FFZ.settings_info.highlight_notifications = {
- type: "boolean",
- value: false,
-
- category: "Chat Filtering",
- no_bttv: true,
- no_mobile: true,
- //visible: function() { return ! this.has_bttv },
-
- name: "Highlight Notifications",
- help: "Display notifications when a highlighted word appears in chat in an unfocused tab. This is automatically disabled on the dashboard.",
-
- on_update: function(val, direct) {
- // Check to see if we have notification permission. If this is
- // enabled, at least.
- if ( ! val || ! direct )
- return;
-
- if ( Notification.permission === "denied" ) {
- this.log("Notifications have been denied by the user.");
- this.settings.set("highlight_notifications", false);
- return;
-
- } else if ( Notification.permission === "granted" )
- return;
-
- var f = this;
- Notification.requestPermission(function(e) {
- if ( e === "denied" ) {
- f.log("Notifications have been denied by the user.");
- f.settings.set("highlight_notifications", false);
- }
- });
- }
- };
-
-
-FFZ.settings_info.notification_timeout = {
- type: "button",
- value: 60,
-
- category: "Chat Filtering",
- no_bttv: true,
- no_mobile: true,
-
- name: "Notification Timeout",
- help: "Specify how long notifications should be displayed before automatically closing.",
-
- method: function() {
- var f = this,
- old_val = this.settings.notification_timeout;
- utils.prompt(
- "Notification Timeout",
- "Please enter the time you'd like notifications to be displayed before automatically closing, in seconds. A value of zero may prevent notifications from disappearing automatically. This value is subject to the limitations of your web browser.Default: 60",
- old_val === false ? 0 : old_val,
- function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
-
- new_val = parseInt(new_val);
- if ( new_val <= 0 )
- new_val = false;
- else if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) )
- new_val = 60;
-
- f.settings.set("notification_timeout", new_val);
- });
- }
- };
-
-
-// ---------------------
-// Socket Commands
-// ---------------------
-
-FFZ.ws_commands.message = function(message) {
- if ( ! this.settings.server_messages )
- return;
-
- this.show_message(message);
-}
-
-
-// ---------------------
-// Notifications
-// ---------------------
-
-FFZ._notifications = {};
-FFZ._last_notification = 0;
-
-FFZ.prototype.clear_notifications = function() {
- for(var k in FFZ._notifications) {
- var n = FFZ._notifications[k];
- if ( n )
- try {
- n.close();
- } catch(err) { }
- }
-
- FFZ._notifications = {};
- FFZ._last_notification = 0;
-}
-
-
-FFZ.prototype.show_notification = function(message, title, tag, timeout, on_click, on_close, icon) {
- var perm = Notification.permission;
- if ( perm === "denied" )
- return false;
-
- if ( perm === "granted" ) {
- title = title || "FrankerFaceZ";
- timeout = timeout || (this.settings.notification_timeout === false ? false : this.settings.notification_timeout * 1000);
-
- var options = {
- lang: "en-US",
- dir: "ltr",
- body: message,
- tag: tag || "FrankerFaceZ",
- icon: icon || "//cdn.frankerfacez.com/icon32.png"
- };
-
- var f = this,
- n = new Notification(title, options),
- nid = FFZ._last_notification++;
-
- FFZ._notifications[nid] = n;
-
- n.addEventListener("click", function() {
- delete FFZ._notifications[nid];
- n.close();
- if ( on_click )
- on_click.bind(f)();
- });
-
- n.addEventListener("close", function() {
- delete FFZ._notifications[nid];
- if ( on_close )
- on_close.bind(f)();
- });
-
- if ( typeof timeout == "number" )
- n.addEventListener("show", function() {
- setTimeout(function() {
- delete FFZ._notifications[nid];
- n.close();
- }, timeout);
- });
-
- return;
- }
-
- var f = this;
- Notification.requestPermission(function(e) {
- f.show_notification(message, title, tag);
- });
-}
-
-
-
-// ---------------------
-// Noty Notification
-// ---------------------
-
-FFZ.prototype.show_message = function(message) {
- if ( ! window.jQuery || ! window.jQuery.noty || ! jQuery.noty.themes.ffzTheme ) {
- setTimeout(this.show_message.bind(this, message), 50);
- return;
- }
-
- window.noty({
- text: message,
- template: '
',
- theme: "ffzTheme",
- layout: "bottomCenter",
- closeWith: ["button"]
- }).show();
-}
\ No newline at end of file
diff --git a/src/ui/popups.js b/src/ui/popups.js
deleted file mode 100644
index cab71b97..00000000
--- a/src/ui/popups.js
+++ /dev/null
@@ -1,90 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants');
-
-
-// ---------------
-// Initialization
-// ---------------
-
-FFZ.prototype.setup_popups = function() {
- this.log("Installing mouse-up event to auto-close pop-ups.");
- var f = this;
-
- jQuery(document).mouseup(function(e) {
- if ( e.button && e.button !== 0 )
- return;
-
- // Check for modal clicks
- var modal = document.getElementById('ffz-modal-container');
- if ( modal && (modal === e.target || modal.contains(e.target)) )
- return;
-
- var popup = f._popup,
- parent = f._popup_parent;
-
- if ( ! popup )
- f._last_popup = undefined;
-
- if ( ! popup || popup === e.target || popup.contains(e.target) )
- return;
-
- if ( popup.id === 'ffz-chat-menu' && popup.style && popup.style.left )
- return;
-
- if ( f._popup_allow_parent ) {
- var parent = f._popup_parent || popup.parentElement;
- if ( parent && ( parent === e.target || parent.contains(e.target) ) )
- return;
- }
-
- f.close_popup();
- });
-}
-
-
-// ---------------
-// Management
-// ---------------
-
-FFZ.prototype.close_popup = function() {
- var popup = this._popup;
- this._last_popup = popup;
- if ( ! popup )
- return;
-
- popup.parentElement.removeChild(popup);
-
- if ( this._popup_kill )
- try {
- this._popup_kill();
- } catch(err) {
- this.error("_popup_kill: " + err);
- }
-
- this._popup = undefined;
- this._popup_parent = undefined;
- this._popup_kill = undefined;
- this._popup_allow_parent = undefined;
- return popup;
-}
-
-
-FFZ.prototype.show_popup = function(el, position, container, cleanup, allow_parent, dont_insert_handler) {
- if ( this._popup )
- this.close_popup();
-
- this._popup = el;
- this._popup_allow_parent = allow_parent || false;
- this._popup_kill = cleanup;
-
- container = container || document.querySelector('.app-main') || document.body;
-
- var bounds = container.getBoundingClientRect();
-
- el.style.display = 'block';
- el.style.position = 'absolute';
- el.style.left = (position[0] - bounds.left) + 'px';
- el.style.top = (position[1] - bounds.top) + 'px';
-
- container.appendChild(el);
-}
\ No newline at end of file
diff --git a/src/ui/races.js b/src/ui/races.js
deleted file mode 100644
index 9c623dfc..00000000
--- a/src/ui/races.js
+++ /dev/null
@@ -1,255 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils');
-
-
-// ---------------
-// Initialization
-// ---------------
-
-FFZ.prototype.setup_races = function() {
- this.log("Initializing race support.");
- this.srl_races = {};
-}
-
-
-// ---------------
-// Settings
-// ---------------
-
-FFZ.settings_info.srl_races = {
- type: "boolean",
- value: true,
- no_mobile: true,
-
- category: "Channel Metadata",
- name: "SRL Race Information",
- help: 'Display information about SpeedRunsLive races under channels.',
- on_update: function(val) {
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('srl_race');
- }
-};
-
-
-// ---------------
-// Socket Handler
-// ---------------
-
-FFZ.ws_on_close.push(function() {
- var controller = utils.ember_lookup('controller:channel'),
- current_id = controller && controller.get('channelModel.id'),
- current_host = controller && controller.get('channelModel.hostModeTarget.id'),
- need_update = false;
-
- if ( ! controller )
- return;
-
- for(var chan in this.srl_races) {
- delete this.srl_races[chan];
- if ( chan === current_id || chan === current_host )
- need_update = true;
- }
-
- if ( need_update && this._cindex )
- this._cindex.ffzUpdateMetadata('srl_race');
-});
-
-
-FFZ.ws_commands.srl_race = function(data) {
- var controller = utils.ember_lookup('controller:channel'),
- current_id = controller && controller.get('channelModel.id'),
- current_host = controller && controller.get('channelModel.hostModeTarget.id'),
- need_update = false;
-
- this.srl_races = this.srl_races || {};
-
- for(var i=0; i < data[0].length; i++) {
- var channel_id = data[0][i];
- this.srl_races[channel_id] = data[1];
- if ( channel_id === current_id || channel_id === current_host )
- need_update = true;
- }
-
- if ( data[1] ) {
- var race = data[1],
- tte = race.twitch_entrants = {};
-
- for(var ent in race.entrants) {
- if ( ! race.entrants.hasOwnProperty(ent) ) continue;
- if ( race.entrants[ent].channel )
- tte[race.entrants[ent].channel] = ent;
- race.entrants[ent].name = ent;
- }
- }
-
- if ( need_update && this._cindex )
- this._cindex.ffzUpdateMetadata('srl_race');
-}
-
-
-// ---------------
-// Race UI
-// ---------------
-
-FFZ.channel_metadata.srl_race = {
- refresh: false,
-
- setup: function(view, channel) {
- var channel_id = channel.get('id'),
- race = this.srl_races && this.srl_races[channel_id],
- entrant_id = race && race.twitch_entrants[channel_id],
- entrant = entrant_id && race.entrants[entrant_id];
-
- return [channel, channel_id, race, entrant];
- },
-
- static_label: ' ',
- label: function(channel, channel_id, race, entrant) {
- if ( ! entrant || ! this.settings.srl_races )
- return null;
-
- return utils.placement(entrant) || '';
- },
-
- tooltip: "SpeedRunsLive Race",
-
- on_popup_close: function(container) {
- if ( this._race_interval ) {
- clearInterval(this._race_interval);
- this._race_interval = null;
- }
- },
-
- update_popup: function(container, channel, channel_id, race, entrant) {
- var now = (Date.now() - (this._ws_server_offset || 0)) / 1000,
- elapsed = Math.floor(now - race.time);
-
- var tbody = container.querySelector('tbody'),
- info = container.querySelector('.heading > div'),
- timer = container.querySelector('.heading > span');
-
- if ( info.getAttribute('data-game') != race.game || info.getAttribute('data-goal') != race.goal ) {
- info.setAttribute('data-game', race.game);
- info.setAttribute('data-goal', race.goal);
-
- var game = utils.quote_san(race.game),
- goal = utils.unquote_attr(race.goal);
-
- goal = goal ? this.render_tokens(this.tokenize_line("jtv", null, goal, true)) : '';
- info.innerHTML = 'Goal: ' + goal + ' ';
- }
-
- if ( race.time != timer.getAttribute('data-time') ) {
- timer.setAttribute('data-time', race.time);
- timer.setAttribute('original-title', race.time ? 'Started at: ' + utils.sanitize(utils.parse_date(1000 * race.time).toLocaleString()) + ' ' : '');
- }
-
- if ( ! elapsed )
- timer.innerHTML = 'Entry Open';
- else
- timer.innerHTML = utils.time_to_string(elapsed);
-
-
- var entrants = [],
- done = true;
-
- for(var ent in race.entrants) {
- var e = race.entrants[ent];
- if ( e.state === 'racing' )
- done = false;
-
- entrants.push(e);
- }
-
- entrants.sort(function(a,b) {
- var a_place = a.place || 9999,
- b_place = b.place || 9999;
-
- if ( a.state === 'forfeit' || a.state === 'dq' )
- a_place = 10000;
- if ( b.state === 'forfeit' || b.state === 'dq' )
- b_place = 10000;
-
- if ( a_place < b_place ) return -1;
- else if ( a_place > b_place ) return 1;
-
- else if ( a.name < b.name ) return -1;
- else if ( a.name > b.name ) return 1;
- });
-
- for(var i=0; i < entrants.length; i++) {
- var ent = entrants[i],
- line = tbody.children[i],
- matching = false;
- if ( line ) {
- matching = line.getAttribute('data-entrant') === ent.name;
- if ( ! matching )
- jQuery('.html-tooltip', line).trigger('mouseout');
-
- } else {
- line = utils.createElement('tr', 'html-tooltip');
- tbody.appendChild(line);
- }
-
- var place = utils.place_string(ent.place),
- comment = ent.comment ? utils.quote_san(ent.comment) : '',
- time = elapsed ? utils.time_to_string(ent.time || elapsed) : '';
-
- if ( ! matching ) {
- var name = '' + this.format_display_name(ent.display_name, ent.name)[0] + ' ',
- twitch_link = ent.channel ? ' ' : '',
- hitbox_link = ent.hitbox ? ' ' : '';
-
- line.setAttribute('data-entrant', ent.name);
- line.innerHTML = '' + name + ' ' + twitch_link + hitbox_link + ' ';
- }
-
- line.setAttribute('original-title', comment);
- line.setAttribute('data-state', ent.state);
- line.children[0].textContent = place;
- line.children[3].textContent = ent.state === 'forfeit' ? 'Forfeit' : time;
- }
-
- while(tbody.children.length > entrants.length)
- tbody.removeChild(tbody.children[entrants.length]);
- },
-
- popup: function(container, channel, channel_id, race, entrant) {
- if ( this._race_interval )
- clearInterval(this._race_interval);
-
- container.classList.add('balloon--md');
-
- var link = 'http://kadgar.net/live',
- has_racing_entrant = false;
-
- for(var ent in race.entrants) {
- var state = race.entrants[ent].state,
- e_channel = race.entrants[ent].channel
- if ( e_channel && (state === 'racing' || state === 'entered') ) {
- link += '/' + e_channel;
- has_racing_entrant = true;
- }
- }
-
- var display_name = channel.get('displayName'),
- tweet = encodeURIComponent("I'm watching " + display_name + " race " + race.goal + " in " + race.game + " on SpeedRunsLive! Watch at"),
- height = Math.max(300, document.querySelector('#player').clientHeight - 100);
-
- container.innerHTML = '' +
- '' +
- '# Entrant Time ' +
- '
' +
-
- '' +
-
- 'SRL ' +
- (has_racing_entrant ? ' Multitwitch ' : '') +
- '
';
-
- var func = FFZ.channel_metadata.srl_race.update_popup.bind(this, container, channel, channel_id, race, entrant);
-
- func();
- this._race_interval = setInterval(func, 1000);
- }
-};
\ No newline at end of file
diff --git a/src/ui/schedule.js b/src/ui/schedule.js
deleted file mode 100644
index 7d4a1e5c..00000000
--- a/src/ui/schedule.js
+++ /dev/null
@@ -1,218 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils'),
-
- TimeFormat = Intl.DateTimeFormat(undefined, {hour: 'numeric', minute: 'numeric'}),
-
- parse_schedule_dates = function(data) {
- for(var event_id in data) {
- var event = data[event_id];
- if ( ! event || ! data.hasOwnProperty(event_id) )
- continue;
-
- event.starttime = utils.parse_date(event.starttime);
- event.endtime = utils.parse_date(event.endtime);
- }
-
- return data
- };
-
-
-// ---------------
-// Settings
-// ---------------
-
-FFZ.settings_info.metadata_schedule = {
- type: "boolean",
- value: true,
- no_mobile: true,
-
- category: "Channel Metadata",
- name: "Event Schedule (Beta) ",
- help: 'Display schedule information under the stream for channels that support it.',
- on_update: function(val) {
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('schedule');
- }
-};
-
-
-// ----------------
-// Data Handler
-// ----------------
-
-FFZ.ws_on_close.push(function() {
- this._schedule_data = {};
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('schedule');
-});
-
-
-FFZ.ws_commands.event_schedule = function(data) {
- var schedules = this._schedule_data = this._schedule_data || {},
- has_schedule = data[1],
- important_events = data[2];
-
- if ( important_events )
- parse_schedule_dates(important_events);
-
- for(var i=0; i < data[0].length; i++)
- schedules[data[0][i]] = [has_schedule, important_events];
-
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('schedule');
-}
-
-
-// ----------------
-// Event Schedules
-// ----------------
-
-FFZ.channel_metadata.schedule = {
- refresh: false,
-
- setup: function(view, channel) {
- var data = this._schedule_data,
- channel_id = channel.get('id'),
- cdata = data && data[channel_id] || [false, [null, null]];
-
- return [channel_id, cdata[0], cdata[1]];
- },
-
- order: 96,
- host_order: 5,
-
- button: true,
-
- static_label: constants.CLOCK,
- label: function(channel_id, has_schedule, important_events) {
- if ( ! this.settings.metadata_schedule || ! has_schedule )
- return null;
-
- return 'Schedule';
- },
-
- tooltip: function(channel_id, has_schedule, important_events) {
- var current = important_events[0],
- next = important_events[1],
- out = [],
-
- format = function(run) {
- return utils.sanitize(
- run.name + ' (' + run.category + ') by ' +
- utils.human_join(_.map(run.runners, function(x) {
- if ( typeof x === 'string' )
- return x;
- return x[1];
- }))
- );
- };
-
- if ( current )
- out.push('Now: ' + format(current));
-
- if ( next && next.starttime )
- out.push(
- utils.full_human_time((Date.now() - next.starttime) / 1000).capitalize() + ': ' +
- format(next));
-
- return out.join(' ');
- },
-
- popup: function(container, channel_id, has_schedule, important_events) {
- container.classList.add('balloon--xl');
- container.innerHTML = '
';
- var t = this,
- loaded = false,
- fail = function() {
- if ( loaded || ! document.body.contains(container) || container.dataset.key !== 'schedule' )
- return;
- container.innerHTML = 'There was an error fetching schedule data from the server.
';
- };
-
- this.ws_send("get_schedule", channel_id, function(success, data) {
- if ( ! success )
- return fail();
- else if ( ! document.body.contains(container) || container.dataset.key !== 'schedule' )
- return;
-
- loaded = true;
- parse_schedule_dates(data);
-
- var scroller = utils.createElement('ol', 'scroller');
- container.innerHTML = '';
- container.appendChild(scroller);
-
- var runs = [];
- for(var run_id in data)
- runs.push([run_id, data[run_id]]);
-
- runs.sort(function(a,b) {
- var ao = a[1].order,
- bo = b[1].order;
-
- if ( ao < bo ) return -1;
- if ( ao > bo ) return 1;
- return 0;
- });
-
- var last_date;
- for(var i=0; i < runs.length; i++)
- last_date = FFZ.channel_metadata.schedule.draw_row.call(t, scroller, runs[i][0], runs[i][1], last_date);
-
- setTimeout(function() {
- var current = scroller.querySelector('.ffz-current-item');
- current && current.scrollIntoViewIfNeeded();
- });
- });
-
- setTimeout(fail, 5000);
- },
-
- draw_row: function(container, run_id, run, last_date) {
- var el = utils.createElement('li', 'ffz-schedule-row'),
- now = Date.now(),
- is_current = run.starttime <= now && run.endtime >= now,
- is_old = run.starttime < now,
- current_date = run.starttime && run.starttime.toLocaleDateString();
-
- if ( current_date !== last_date )
- container.appendChild(utils.createElement('div', 'ffz-schedule-row ffz-schedule-date', current_date));
-
- el.classList.toggle('ffz-current-item', is_current);
- el.classList.toggle('ffz-old-item', ! is_current && is_old);
- el.dataset.id = run_id;
-
- var meta = [
- 'Length: ' + utils.sanitize(run.run_time)
- ];
-
- if ( run.setup_time )
- meta.push('Setup: ' + utils.sanitize(run.setup_time));
-
- if ( run.coop )
- meta.push('Co-Op');
-
- if ( run.console )
- meta.push('Console: ' + utils.sanitize(run.console));
-
- el.innerHTML = '' +
- '
' + utils.sanitize(run.name) + ' (' + utils.sanitize(run.category).replace(/ +/g, ' ') + ') ' +
- '' +
- (run.starttime ? '' + utils.sanitize(TimeFormat.format(run.starttime)) + ' ' : '') +
- '' + meta.join(' — ') + '
' +
- 'Runner' + utils.pluralize(run.runners) + ': ' +
- utils.human_join(_.map(run.runners, function(x) {
- if ( typeof x === 'string' )
- x = [x,x];
-
- if ( x[0] )
- return '
' + utils.sanitize(x[1]) + ' ';
- return '
' + utils.sanitize(x[1]) + ' ';
- })) +
- '
';
-
- container.appendChild(el);
- return current_date;
- }
-}
\ No newline at end of file
diff --git a/src/ui/styles.js b/src/ui/styles.js
deleted file mode 100644
index 42af486c..00000000
--- a/src/ui/styles.js
+++ /dev/null
@@ -1,75 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants');
- styles = require('../compiled_styles');
-
-FFZ.prototype.setup_css = function() {
- document.body.classList.toggle('ffz-flip-dashboard', this.settings.flip_dashboard);
-
- this.log("Injecting main FrankerFaceZ CSS.");
-
- var s = this._main_style = document.createElement('link');
- s.id = "ffz-main-css";
- s.setAttribute('rel', 'stylesheet');
- s.setAttribute('href', constants.SERVER + "script/style" + (this.is_clips ? '-clips' : '') + (constants.DEBUG ? "" : ".min") + ".css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
- document.head.appendChild(s);
-
- this.log("Readying toggleable styles.");
- this._toggle_style_state = {};
-
- s = this._toggle_style = document.createElement('style');
- s.type = "text/css";
- s.id = "ffz-toggle-css";
- document.head.appendChild(s);
-
- /*var s = this._main_style = document.createElement('style');
-
- s.textContent = styles.style;
- s.id = "ffz-main-css";
-
- document.head.appendChild(s);*/
-
- if ( window.jQuery && jQuery.noty )
- jQuery.noty.themes.ffzTheme = {
- name: "ffzTheme",
- style: function() {
- this.$bar.removeClass().addClass("noty_bar").addClass("ffz-noty").addClass(this.options.type);
- },
- callback: {
- onShow: function() {},
- onClose: function() {}
- }
- };
-}
-
-
-FFZ.prototype._scroll_fixed = null;
-
-FFZ.prototype.fix_scroll = function() {
- if ( this._scroll_fixed )
- return;
-
- var f = this,
- main = document.querySelector('.app-main');
-
- if ( main ) {
- this._scroll_fixed = true;
- main.addEventListener('scroll', function() {
- if ( this.scrollTop !== 0 ) {
- f.log("The application scrolled wrongly. Correcting.");
- this.scrollTop = 0;
- }
- });
- }
-}
-
-
-FFZ.prototype.toggle_style = function(key, enabled) {
- var state = this._toggle_style_state[key];
- if ( (enabled && state) || (!enabled && !state) )
- return;
-
- this._toggle_style_state[key] = enabled;
-
- utils.update_css(this._toggle_style, key, enabled ? styles[key] || null : null);
-}
\ No newline at end of file
diff --git a/src/ui/sub_count.js b/src/ui/sub_count.js
deleted file mode 100644
index 7902d4ea..00000000
--- a/src/ui/sub_count.js
+++ /dev/null
@@ -1,87 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils');
-
-
-// -------------------
-// Subscriber Display
-// -------------------
-
-FFZ.prototype._update_subscribers = function() {
- if ( this._update_subscribers_timer )
- clearTimeout(this._update_subscribers_timer);
-
- var f = this,
- user = this.get_user();
-
- if ( this.has_bttv || ! user || ! user.login || this.dashboard_channel !== user.login )
- return jQuery("#ffz-sub-display").remove();
-
- // Schedule an update.
- this._update_subscribers_timer = setTimeout(this._update_subscribers.bind(this), 60000);
-
- // Get the count!
- utils.api.get("/api/channels/" + this.dashboard_channel + "/subscriber_count").done(function(data) {
- var el, sub_count = data && data.count;
- if ( typeof sub_count === "string" )
- sub_count = parseInt(sub_count.replace(/[,\.]/g, ""));
-
- if ( typeof sub_count !== "number" || isNaN(sub_count) || ! isFinite(sub_count) ) {
- jQuery("#ffz-sub-display").remove();
-
- var failed = f._failed_sub_checks = (f._failed_sub_checks || 0) + 1;
- if ( f._update_subscribers_timer && failed >= 5 ) {
- f.log("Subscriber count failed 5 times. Giving up.");
- clearTimeout(f._update_subscribers_timer);
- f._update_subscribers_timer = undefined;
- }
-
- return;
- }
-
- // Graph this glorious data point
- if ( f._dash_chart ) {
- if ( ! f._dash_chart.series[3].options.showInLegend ) {
- f._dash_chart.series[3].options.showInLegend = true;
- f._dash_chart.legend.renderLegend();
- }
-
- f._dash_chart.series[3].addPoint({x: utils.last_minute(), y: sub_count});
- }
-
- el = document.querySelector('#ffz-sub-display span');
- if ( ! el ) {
- var cont = document.querySelector('#stats');
- if ( ! cont )
- return;
-
- var stat = utils.createElement('span', 'ffz stat', constants.STAR + ' ');
- stat.id = 'ffz-sub-display';
- stat.title = 'Subscribers';
-
- el = utils.createElement('span');
- stat.appendChild(el);
-
- utils.api.get("chat/" + f.dashboard_channel + "/badges", null, {version: 3})
- .done(function(data) {
- if ( data.subscriber && data.subscriber.image ) {
- stat.innerHTML = '';
- stat.appendChild(el);
-
- stat.style.backgroundImage = 'url("' + data.subscriber.image + '")';
- stat.style.backgroundRepeat = 'no-repeat';
- stat.style.paddingLeft = '23px';
- stat.style.backgroundPosition = '0 50%';
- }
- });
-
- cont.appendChild(stat);
- jQuery(stat).zipsy({gravity: 's'});
- }
-
- el.textContent = utils.number_commas(sub_count);
-
- }).fail(function(){
- jQuery("#ffz-sub-display").remove();
- });
-}
\ No newline at end of file
diff --git a/src/ui/tooltips.js b/src/ui/tooltips.js
deleted file mode 100644
index af245880..00000000
--- a/src/ui/tooltips.js
+++ /dev/null
@@ -1,428 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants');
-
-
-// ---------------------
-// Initialization
-// ---------------------
-
-FFZ.prototype.fix_tooltips = function() {
- // Add handlers to FFZ's tooltip classes.
- jQuery(".html-tooltip").zipsy({
- live: true,
- html: true,
- gravity: utils.newtip_placement(constants.TOOLTIP_DISTANCE, 'n')
- //gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')
- });
-
- jQuery(".ffz-tooltip").zipsy({
- live: true,
- html: true,
- className: this.render_tooltip_class(),
- title: this.render_tooltip(),
- gravity: utils.newtip_placement(constants.TOOLTIP_DISTANCE, 'n')
- //gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')
- });
-
- // First, override the tooltip mixin.
- var TipsyTooltip = utils.ember_resolve('component:tipsy-tooltip');
- if ( TipsyTooltip ) {
- this.log("Modifying Tipsy-Tooltip component to use gravity.");
- TipsyTooltip.reopen({
- didInsertElement: function() {
- var gravity = this.get("gravity");
- if ( ! gravity || typeof gravity === "string" )
- gravity = utils.newtip_placement(constants.TOOLTIP_DISTANCE, gravity || 's');
-
- this.$().zipsy({
- gravity: gravity
- });
- }
- })
- }
-
- // Fix tipsy invalidation
- if ( window.jQuery && jQuery.fn && jQuery.fn.tipsy )
- jQuery.fn.tipsy.revalidate = function() {
- jQuery(".tipsy").each(function() {
- var t = jQuery.data(this, "tipsy-pointee");
- (!t || !t[0] || !document.contains(t[0])) && jQuery(this).remove();
- })
- };
-
-
- // Iterate all existing tipsy stuff~!
- this.log('Fixing already existing tooltips.');
- if ( ! window.jQuery || ! jQuery.cache )
- return;
-
- for(var obj_id in jQuery.cache) {
- var obj = jQuery.cache[obj_id];
- if ( obj && obj.data && obj.data.tipsy && obj.data.tipsy.options && typeof obj.data.tipsy.options.gravity !== "function" ) {
- obj.data.tipsy.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, obj.data.tipsy.options.gravity || 's');
- }
- }
-}
-
-
-// ---------------------
-// Zipsy!
-// ---------------------
-
-var zipsyIdcounter = 0;
-
-function maybeCall(thing, ctx) {
- return typeof thing === 'function' ? thing.apply(ctx, Array.prototype.slice.call(arguments, 2)) : thing;
-}
-
-function Zipsy(element, options) {
- this.$element = jQuery(element);
- this.options = options;
- this.enabled = true;
- this.fixTitle();
-}
-
-Zipsy.prototype = {
- show: function() {
- var j_el = this.$element,
- el = j_el[0];
-
- if ( ! this.enabled || ! document.contains(el) || ! j_el.is(':visible') )
- return;
-
- var title = this.getTitle(),
- $tip = this.tip();
-
- if ( ! title )
- return;
-
- $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
- $tip[0].className = 'zipsy tipsy';
- if ( this.options.className )
- $tip.addClass(maybeCall(this.options.className, el));
-
- var prepend_to = maybeCall(this.options.prependTo, el);
- if ( typeof prepend_to === 'string' )
- prepend_to = document.querySelector(prepend_to);
- else if ( typeof prepend_to === 'object' && !(prepend_to instanceof HTMLElement) )
- prepend_to = prepend_to[0];
- else if ( ! prepend_to )
- prepend_to = document.body;
-
- if ( ! prepend_to )
- return;
-
- $tip.detach().css({
- top: 0, left: 0,
- width: '', height: '',
- visibility: 'hidden',
- display: 'block'
- }).prependTo(prepend_to).data('tipsy-pointee', el);
-
- var pos;
-
- if ( j_el.parents('svg').length > 0 )
- pos = jQuery.extend({}, j_el.offset(), el.getBBox());
-
- else {
- var el_box = el.getBoundingClientRect(),
- cont_box = prepend_to.getBoundingClientRect();
-
- pos = {
- top: el_box.top - cont_box.top,
- left: el_box.left - cont_box.left,
- width: el_box.width,
- height: el_box.height
- }
-
- }
-
- var bbox = $tip[0].getBoundingClientRect(),
- actual_width = Math.ceil(bbox.width),
- actual_height = Math.ceil(bbox.height),
- gravity = maybeCall(this.options.gravity, el, bbox),
- g1 = gravity.charAt(0),
- g2 = gravity.length > 1 ? gravity.charAt(1) : 'c',
- offset = maybeCall(this.options.offset, el),
- tp = {};
-
- if ( g1 === 'n' )
- tp.top = pos.top + pos.height + offset;
-
- else if ( g1 === 's' )
- tp.top = pos.top - actual_height - offset;
-
- else if ( g1 === 'e' )
- tp.left = pos.left - actual_width - offset;
-
- else if ( g1 === 'w' )
- tp.left = pos.left + pos.width + offset;
-
- if ( g1 === 'n' || g1 === 's' ) {
- if ( g2 === 'c' )
- tp.left = pos.left + pos.width / 2 - actual_width / 2;
-
- else if ( g2 === 'e' )
- tp.left = pos.left + pos.width - actual_width + 5;
-
- else if ( g2 === 'w' )
- tp.left = pos.left - 5;
-
- } else if ( g1 === 'e' || g1 === 'w' ) {
- if ( g2 === 'c' )
- tp.top = pos.top + pos.height / 2 - actual_height / 2;
-
- else if ( g2 === 'n' )
- tp.top = pos.top - 5;
-
- else if ( g2 === 's' )
- tp.top = pos.top + pos.height - actual_height + 5;
- }
-
- $tip.css(tp).addClass('tipsy-' + gravity);
- $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
- $tip.css({width: actual_width + 'px'});
-
- var opacity = maybeCall(this.options.opacity, el);
-
- if ( this.options.fade )
- $tip.stop().css({
- opacity: 0,
- display: 'block',
- visibility: 'hidden'
- }).animate({
- opacity: opacity
- }, this.options.fadeInTime);
-
- else
- $tip.css({
- visibility: 'visible',
- opacity: opacity
- });
-
- if ( this.options.aria ) {
- var tip_id = zipsyIdcounter++;
- $tip.attr('id', tip_id);
- j_el.attr('aria-describedby', tip_id);
- }
- },
-
- hide: function() {
- if ( this.options.fade )
- this.tip().stop().fadeOut(this.options.fadeOutTime, function() { jQuery(this).detach() });
- else
- this.tip().detach();
-
- if ( this.options.aria )
- this.$element.removeAttr('aria-describedby');
- },
-
- fixTitle: function() {
- var e = this.$element;
- if ( e.attr('title') || typeof e.attr('original-title') !== 'string' )
- e.attr('original-title', e.attr('title') || '').removeAttr('title');
- },
-
- getTitle: function() {
- var e = this.$element,
- title = this.options.title;
-
- if ( title === 'title' ) {
- this.fixTitle();
- title = e.attr('original-title');
- } else if ( typeof title === 'string' )
- title = e.attr(title);
- else if ( typeof title === 'function' )
- title = title.call(e[0]);
- else
- title = '';
-
- return (''+title).trim() || this.options.fallback;
- },
-
- tip: function() {
- var t = this;
- if ( ! this._tip )
- this._tip = jQuery('');
-
- this._tip
- .off('mouseenter mouseleave')
- .on('mouseenter', function() {
- t.hoverState = 'in';
-
- }).on('mouseleave', function() {
- t.hoverState = 'out';
- if ( t.options.delayOut === 0 )
- t.hide();
- else
- setTimeout(function() {
- if ( t.hoverState === 'out' || ! t.$element || ! t.$element.is(':visible') )
- t.hide();
- }, t.options.delayOut);
- })
-
- return this._tip;
- },
-
- validate: function() {
- if ( ! this.$element[0].parentNode ) {
- this.hide();
- this.$element = null;
- this.options = null;
- this.enabled = false;
- }
- },
-
- enable: function() {
- this.enabled = true;
- },
-
- disable: function() {
- this.enabled = false;
- },
-
- toggleEnabled: function() {
- this.enabled = !this.enabled;
- }
-}
-
-var JQ_Zipsy = function(options) {
- JQ_Zipsy.enable();
-
- if ( options === true )
- return this.data('tipsy');
-
- else if ( typeof options === 'string' ) {
- var tipsy = this.data('tipsy');
- if ( tipsy )
- tipsy[options]();
-
- return this;
- }
-
- options = jQuery.extend({}, JQ_Zipsy.defaults, options);
-
- function get(el) {
- var zipsy = jQuery.data(el, 'tipsy');
- if ( ! zipsy ) {
- zipsy = new Zipsy(el, JQ_Zipsy.elementOptions(el, options));
- jQuery.data(el, 'tipsy', zipsy);
- }
-
- return zipsy;
- }
-
- function enter() {
- if ( ! JQ_Zipsy.enabled )
- return;
-
- var zipsy = get(this);
- zipsy.hoverState = 'in';
- if ( options.delayIn === 0 )
- zipsy.show();
- else
- setTimeout(function() {
- if ( zipsy.hoverState === 'in' )
- zipsy.show();
- }, options.delayIn);
- }
-
- function leave() {
- var zipsy = get(this);
- zipsy.hoverState = 'out';
- if ( options.delayOut === 0 )
- zipsy.hide();
- else
- setTimeout(function() {
- if ( zipsy.hoverState === 'out' || ! zipsy.$element || ! zipsy.$element.is(':visible') )
- zipsy.hide();
- }, options.delayOut);
- }
-
- if ( ! options.live )
- this.each(function() { get(this) });
-
- if ( options.trigger !== 'manual' ) {
- var event_in = options.trigger === 'hover' ? 'mouseenter mouseover' : 'focus',
- event_out = options.trigger === 'hover' ? 'mouseleave mouseout' : 'blur';
-
- if ( options.live && options.live !== true ) {
- jQuery(this).on(event_in, options.live, enter);
- jQuery(this).on(event_out, options.live, leave);
- } else if ( options.live ) {
- jQuery(document.body).on(event_in, this.selector, enter);
- jQuery(document.body).on(event_out, this.selector, leave);
-
- } else {
- var binder = options.live ? 'live' : 'bind';
- this[binder](event_in, enter)[binder](event_out, leave);
- }
- }
-
- return this;
-}
-
-JQ_Zipsy.defaults = {
- aria: false,
- className: null,
- delayIn: 0,
- delayOut: 0,
- fade: false,
- fadeInTime: 400,
- fadeOutTime: 400,
- fallback: '',
- gravity: 'n',
- html: false,
- live: false,
- offset: 0,
- opacity: 0.8,
- title: 'title',
- trigger: 'hover',
- theme: '',
- prependTo: document.body
-};
-
-JQ_Zipsy.revalidate = function() {
- jQuery('.tipsy').each(function() {
- var t = jQuery.data(this, "tipsy-pointee");
- (!t || !t[0] || !document.contains(t[0])) && jQuery(this).remove();
- });
-}
-
-JQ_Zipsy.clear = function() {
- jQuery('.tipsy').remove();
-}
-
-JQ_Zipsy.enable = function() {
- JQ_Zipsy.enabled = true;
-}
-
-JQ_Zipsy.disable = function() {
- JQ_Zipsy.enabled = false;
-}
-
-JQ_Zipsy.elementOptions = function(el, options) {
- return options;
-}
-
-JQ_Zipsy.autoNS = function() {
- return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
-}
-
-JQ_Zipsy.autoWE = function() {
- return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
-}
-
-JQ_Zipsy.autoNWNE = function() {
- return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'ne' : 'nw';
-}
-
-JQ_Zipsy.autoSWSE = function() {
- return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'se' : 'sw';
-}
-
-JQ_Zipsy.autoBounds = utils.newtip_placement;
-
-if ( window.jQuery && jQuery.fn )
- jQuery.fn.zipsy = JQ_Zipsy;
diff --git a/src/ui/viewer_count.js b/src/ui/viewer_count.js
deleted file mode 100644
index 78154a24..00000000
--- a/src/ui/viewer_count.js
+++ /dev/null
@@ -1,72 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('../constants'),
- utils = require('../utils');
-
-
-// ------------
-// FFZ Viewers
-// ------------
-
-FFZ.ws_commands.chatters = function(data) {
- var channel = data[0], count = data[1];
-
- var controller = utils.ember_lookup('controller:channel'),
- match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined,
- id = this.is_dashboard ? match && match[1] : controller && controller.get && controller.get('id');
-
- if ( ! this.is_dashboard ) {
- var room = this.rooms && this.rooms[channel];
- if ( room ) {
- room.ffz_chatters = count;
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('chatters');
- }
- return;
- }
-
- this._dash_chatters = count;
-}
-
-FFZ.ws_commands.viewers = function(data) {
- var channel = data[0], count = data[1];
-
- var controller = utils.ember_lookup('controller:channel'),
- match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined,
- id = this.is_dashboard ? match && match[1] : controller && controller.get && controller.get('id');
-
- if ( ! this.is_dashboard ) {
- var room = this.rooms && this.rooms[channel];
- if ( room ) {
- room.ffz_viewers = count;
- if ( this._cindex )
- this._cindex.ffzUpdateMetadata('chatters');
- }
- return;
- }
-
- this._dash_viewers = count;
-
- if ( ! this.settings.chatter_count || id !== channel )
- return;
-
- var view_count = document.querySelector('#ffz-ffzchatter-display'),
- content = constants.ZREKNARF + ' ' + utils.number_commas(count) + (typeof this._dash_chatters === "number" ? ' (' + utils.number_commas(this._dash_chatters) + ')' : "");
-
- if ( view_count )
- view_count.innerHTML = content;
- else {
- var parent = document.querySelector("#stats");
- if ( ! parent )
- return;
-
- view_count = document.createElement('span');
- view_count.id = "ffz-ffzchatter-display";
- view_count.className = 'ffz stat';
- view_count.title = 'Viewers (In Chat) with FrankerFaceZ';
- view_count.innerHTML = content;
-
- parent.appendChild(view_count);
- jQuery(view_count).zipsy({
- gravity: this.is_dashboard ? "s" : utils.newtip_placement(constants.TOOLTIP_DISTANCE, 'n')});
- }
-}
\ No newline at end of file
diff --git a/src/colors.js b/src/utilities/color.js
similarity index 54%
rename from src/colors.js
rename to src/utilities/color.js
index d576cfb2..34206ab7 100644
--- a/src/colors.js
+++ b/src/utilities/color.js
@@ -1,177 +1,44 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('./utils'),
+'use strict';
+/* eslint-disable */
- hue2rgb = function(p, q, t) {
- if ( t < 0 ) t += 1;
- if ( t > 1 ) t -= 1;
- if ( t < 1/6 )
- return p + (q-p) * 6 * t;
- if ( t < 1/2 )
- return q;
- if ( t < 2/3 )
- return p + (q-p) * (2/3 - t) * 6;
- return p;
- },
+import {has} from 'utilities/object';
- bit2linear = function(channel) {
- // http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html
- // This converts rgb 8bit to rgb linear, lazy because the other algorithm is really really dumb
- //return Math.pow(channel, 2.2);
+export function hue2rgb(p, q, t) {
+ if ( t < 0 ) t += 1;
+ if ( t > 1 ) t -= 1;
+ if ( t < 1/6 )
+ return p + (q-p) * 6 * t;
+ if ( t < 1/2 )
+ return q;
+ if ( t < 2/3 )
+ return p + (q-p) * (2/3 - t) * 6;
+ return p;
+}
- // CSS Colors Level 4 says 0.03928, Bruce Lindbloom who cared to write all algos says 0.04045, used bruce because whynawt
- return (channel <= 0.04045) ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
- },
+export function bit2linear(channel) {
+ // http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html
+ // This converts rgb 8bit to rgb linear, lazy because the other algorithm is really really dumb
+ //return Math.pow(channel, 2.2);
- linear2bit = function(channel) {
- // Using lazy conversion in the other direction as well
- //return Math.pow(channel, 1/2.2);
+ // CSS Colors Level 4 says 0.03928, Bruce Lindbloom who cared to write all algos says 0.04045, used bruce because whynawt
+ return (channel <= 0.04045) ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
+}
- // I'm honestly not sure about 0.0031308, I've only seen it referenced on Bruce Lindbloom's site
- return (channel <= 0.0031308) ? channel * 12.92 : Math.pow(1.055 * channel, 1/2.4) - 0.055;
- };
+export function linear2bit(channel) {
+ // Using lazy conversion in the other direction as well
+ //return Math.pow(channel, 1/2.2);
-
-// ---------------------
-// Settings
-// ---------------------
-
-FFZ.settings_info.fix_color = {
- type: "select",
- options: {
- '-1': "Disabled",
- 0: ["Default (Unprocessed)", 1],
- 6: ["HSL Luma (New Default)", 2],
- 1: ["Luv Luma (Old Default)", 3],
- 5: ["HSL Loop (BTTV-Like)", 4],
-
- 2: ["HSL Adjustment (Deprecated)", 5],
- 3: ["HSV Adjustment (Deprecated)", 6],
- 4: ["RGB Adjustment (Deprecated)", 7],
- },
-
- value: 6,
-
- category: "Chat Appearance",
- no_bttv: true,
-
- name: "Username Colors",
- help: "Ensure that username colors contrast with the background enough to be readable.",
-
- process_value: utils.process_int(6, 0, 6),
-
- on_update: function(val) {
- this.toggle_style('chat-colors-gray', !this.has_bttv && val === -1);
-
- if ( ! this.has_bttv && val !== -1 )
- this._rebuild_colors();
- }
-};
-
-
-FFZ.settings_info.luv_contrast = {
- type: "button",
- value: 4.5,
-
- category: "Chat Appearance",
- no_bttv: true,
-
- name: "Username Colors - Luv Minimum Contrast",
- help: "Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.",
-
- method: function() {
- var f = this,
- old_val = this.settings.luv_contrast;
-
- utils.prompt(
- "Luv Adjustment Minimum Contrast Ratio",
- "Please enter a new value for the minimum contrast ratio required between username colors and the background.Default: 4.5",
- old_val,
- function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
-
- var parsed = parseFloat(new_val);
- if ( Number.isNaN(parsed) || ! Number.isFinite(parsed) )
- parsed = 4.5;
-
- f.settings.set("luv_contrast", parsed);
- });
- },
-
- on_update: function(val) {
- this._rebuild_contrast();
- this._rebuild_filter_styles();
-
- if ( ! this.has_bttv && this.settings.fix_color === 1 )
- this._rebuild_colors();
- }
-};
-
-
-FFZ.settings_info.color_blind = {
- type: "select",
- options: {
- 0: "Disabled",
- protanope: "Protanope",
- deuteranope: "Deuteranope",
- tritanope: "Tritanope"
- },
- value: '0',
-
- category: "Chat Appearance",
- no_bttv: true,
-
- name: "Username Colors - Color Blindness",
- help: "Adjust username colors in an attempt to make them more distinct for people with color blindness.",
-
- on_update: function(val) {
- if ( ! this.has_bttv && this.settings.fix_color !== -1 )
- this._rebuild_colors();
- }
-};
-
-
-// --------------------
-// Initialization
-// --------------------
-
-FFZ.prototype.setup_colors = function() {
- this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === -1);
-
- this._hex_colors = {};
- this._rebuild_contrast();
-
- this._update_colors();
-
- // Events for rebuilding colors.
- var Layout = utils.ember_lookup('service:layout'),
- ThemeManager = utils.ember_lookup('service:theme-manager'),
- Settings = utils.ember_settings();
-
- if ( Layout )
- Layout.addObserver("isTheatreMode", this._update_colors.bind(this, true));
-
- if ( Settings )
- Settings.addObserver("darkMode", this._update_colors.bind(this, true))
-
- if ( ThemeManager )
- ThemeManager.addObserver("themes.activeTheme", this._update_colors.bind(this, true));
-
-
- this._color_old_darkness = (ThemeManager && ThemeManager.get('themes.activeTheme') === 'theme--dark') || (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('darkMode'));
+ // I'm honestly not sure about 0.0031308, I've only seen it referenced on Bruce Lindbloom's site
+ return (channel <= 0.0031308) ? channel * 12.92 : Math.pow(1.055 * channel, 1/2.4) - 0.055;
}
-// -----------------------
-// Color Handling Classes
-// -----------------------
+export const Color = {};
-FFZ.Color = {};
+Color._canvas = null;
+Color._context = null;
-FFZ.Color._canvas = null;
-FFZ.Color._context = null;
-
-FFZ.Color.CVDMatrix = {
+Color.CVDMatrix = {
protanope: [ // reds are greatly reduced (1% men)
0.0, 2.02344, -2.52581,
0.0, 1.0, 0.0,
@@ -190,23 +57,23 @@ FFZ.Color.CVDMatrix = {
}
-var RGBAColor = FFZ.Color.RGBA = function(r, g, b, a) {
+const RGBAColor = Color.RGBA = function(r, g, b, a) {
this.r = r||0; this.g = g||0; this.b = b||0; this.a = a||0;
};
-var HSVAColor = FFZ.Color.HSVA = function(h, s, v, a) {
+const HSVAColor = Color.HSVA = function(h, s, v, a) {
this.h = h||0; this.s = s||0; this.v = v||0; this.a = a||0;
};
-var HSLAColor = FFZ.Color.HSLA = function(h, s, l, a) {
+const HSLAColor = Color.HSLA = function(h, s, l, a) {
this.h = h||0; this.s = s||0; this.l = l||0; this.a = a||0;
};
-var XYZAColor = FFZ.Color.XYZA = function(x, y, z, a) {
+const XYZAColor = Color.XYZA = function(x, y, z, a) {
this.x = x||0; this.y = y||0; this.z = z||0; this.a = a||0;
};
-var LUVAColor = FFZ.Color.LUVA = function(l, u, v, a) {
+const LUVAColor = Color.LUVA = function(l, u, v, a) {
this.l = l||0; this.u = u||0; this.v = v||0; this.a = a||0;
};
@@ -218,16 +85,16 @@ RGBAColor.prototype.eq = function(rgb) {
}
RGBAColor.fromName = function(name) {
- var context = FFZ.Color._context;
+ let context = Color._context;
if ( ! context ) {
- var canvas = FFZ.Color._canvas = document.createElement('canvas');
- context = FFZ.Color._context = canvas.getContext("2d");
+ const canvas = Color._canvas = document.createElement('canvas');
+ context = Color._context = canvas.getContext('2d');
}
context.clearRect(0,0,1,1);
context.fillStyle = name;
context.fillRect(0,0,1,1);
- var data = context.getImageData(0,0,1,1);
+ const data = context.getImageData(0,0,1,1);
if ( ! data || ! data.data || data.data.length !== 4 )
return null;
@@ -244,31 +111,31 @@ RGBAColor.fromCSS = function(rgb) {
if ( rgb.charAt(0) === '#' )
return RGBAColor.fromHex(rgb);
- var match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:, *([\d\.]+))?\)/i.exec(rgb);
+ const match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:, *([\d.]+))?\)/i.exec(rgb);
if ( match ) {
- var r = match[1],
+ let r = match[1],
g = match[2],
b = match[3],
a = match[4];
if ( r.charAt(r.length-1) === '%' )
- r = 255 * (parseInt(r) / 100);
+ r = 255 * (parseInt(r,10) / 100);
else
- r = parseInt(r);
+ r = parseInt(r,10);
if ( g.charAt(g.length-1) === '%' )
- g = 255 * (parseInt(g) / 100);
+ g = 255 * (parseInt(g,10) / 100);
else
- g = parseInt(g);
+ g = parseInt(g,10);
if ( b.charAt(b.length-1) === '%' )
- b = 255 * (parseInt(b) / 100);
+ b = 255 * (parseInt(b,10) / 100);
else
- b = parseInt(b);
+ b = parseInt(b,10);
if ( a )
if ( a.charAt(a.length-1) === '%' )
- a = parseInt(a) / 100;
+ a = parseInt(a,10) / 100;
else
a = parseFloat(a);
else
@@ -279,24 +146,40 @@ RGBAColor.fromCSS = function(rgb) {
Math.min(Math.max(0, g), 255),
Math.min(Math.max(0, b), 255),
Math.min(Math.max(0, a), 1)
- );
+ );
}
return RGBAColor.fromName(rgb);
}
-RGBAColor.fromHex = function(code) {
- var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16);
+RGBAColor.fromHex = function(code, alpha = 1) {
+ if ( code.charAt(0) === '#' )
+ code = code.slice(1);
+
+ if ( code.length === 3 )
+ code = `${code[0]}${code[0]}${code[1]}${code[1]}${code[2]}${code[2]}`;
+
+ else if ( code.length === 4 )
+ code = `${code[0]}${code[0]}${code[1]}${code[1]}${code[2]}${code[2]}${code[3]}${code[3]}`;
+
+ if ( code.length === 8 ) {
+ alpha = parseInt(code.slice(6), 16) / 255;
+ code = code.slice(0, 6);
+
+ } else if ( code.length !== 6 )
+ throw new Error('invalid hex code');
+
+ const raw = parseInt(code, 16);
return new RGBAColor(
(raw >> 16), // Red
(raw >> 8 & 0x00FF), // Green
(raw & 0x0000FF), // Blue,
- 1 // Alpha
- )
+ alpha // Alpha
+ );
}
RGBAColor.fromHSVA = function(h, s, v, a) {
- var r, g, b,
+ let r, g, b,
i = Math.floor(h * 6),
f = h * 6 - i,
@@ -322,7 +205,7 @@ RGBAColor.fromHSVA = function(h, s, v, a) {
}
RGBAColor.fromXYZA = function(x, y, z, a) {
- var R = 3.240479 * x - 1.537150 * y - 0.498535 * z,
+ const R = 3.240479 * x - 1.537150 * y - 0.498535 * z,
G = -0.969256 * x + 1.875992 * y + 0.041556 * z,
B = 0.055648 * x - 0.204043 * y + 1.057311 * z;
@@ -337,11 +220,11 @@ RGBAColor.fromXYZA = function(x, y, z, a) {
RGBAColor.fromHSLA = function(h, s, l, a) {
if ( s === 0 ) {
- var v = Math.round(Math.min(Math.max(0, 255*l), 255));
+ const v = Math.round(Math.min(Math.max(0, 255*l), 255));
return new RGBAColor(v, v, v, a === undefined ? 1 : a);
}
- var q = l < 0.5 ? l * (1 + s) : l + s - l * s,
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
return new RGBAColor(
@@ -395,8 +278,8 @@ RGBAColor.prototype.daltonize = function(type, amount) {
amount = typeof amount === "number" ? amount : 1.0;
var cvd;
if ( typeof type === "string" ) {
- if ( FFZ.Color.CVDMatrix.hasOwnProperty(type) )
- cvd = FFZ.Color.CVDMatrix[type];
+ if ( Color.CVDMatrix.hasOwnProperty(type) )
+ cvd = Color.CVDMatrix[type];
else
throw "Invalid CVD matrix.";
} else
@@ -649,196 +532,104 @@ LUVAColor.prototype._v = function(v) { return new LUVAColor(this.l, this.u, v, t
LUVAColor.prototype._a = function(a) { return new LUVAColor(this.l, this.u, this.v, a); }
-// --------------------
-// Rebuild Colors
-// --------------------
-FFZ.prototype._rebuild_contrast = function() {
- this._luv_required_bright = new XYZAColor(0, (this.settings.luv_contrast * (new RGBAColor(35,35,35,1).toXYZA().y + 0.05) - 0.05), 0, 1).toLUVA().l;
- this._luv_required_dark = new XYZAColor(0, ((new RGBAColor(217,217,217,1).toXYZA().y + 0.05) / this.settings.luv_contrast - 0.05), 0, 1).toLUVA().l;
+export class ColorAdjuster {
+ constructor(base = '#232323', mode = 0, contrast = 4.5) {
+ this._contrast = contrast;
+ this._base = base;
+ this._mode = mode;
- this._luv_background_bright = new XYZAColor(0, (this.settings.luv_contrast * (RGBAColor.fromCSS("#3c3a41").toXYZA().y + 0.05) - 0.05), 0, 1).toLUVA().l;
- this._luv_background_dark = new XYZAColor(0, ((RGBAColor.fromCSS("#acacbf").toXYZA().y + 0.05) / this.settings.luv_contrast - 0.05), 0, 1).toLUVA().l;
-
- this._hslluma_required_bright = this.settings.luv_contrast * (RGBAColor.fromCSS("#17141f").luminance() + 0.05) - 0.05;
- this._hslluma_required_dark = (RGBAColor.fromCSS("#efeef1").luminance() + 0.05) / this.settings.luv_contrast - 0.05;
-}
-
-FFZ.prototype._rebuild_colors = function() {
- if ( this.has_bttv )
- return;
-
- // With update colors, we'll automatically process the colors we care about.
- this._hex_colors = {};
- this._update_colors();
-}
-
-
-FFZ.prototype._update_colors = function(darkness_only) {
- // Update the lines. ALL of them.
- var Layout = utils.ember_lookup('service:layout'),
- ThemeManager = utils.ember_lookup('service:theme-manager'),
-
- is_dark = (ThemeManager && ThemeManager.get('themes.activeTheme') === 'theme--dark') || (Layout && Layout.get('isTheatreMode')) || this.settings.get_twitch("darkMode"),
- cr_dark = this.settings.dark_twitch || (Layout && Layout.get('isTheatreMode'));
-
- if ( darkness_only && this._color_old_darkness === is_dark )
- return;
-
- this._color_old_darkness = is_dark;
-
- var colored_bits = document.querySelectorAll('.has-color');
- for(var i=0, l=colored_bits.length; i < l; i++) {
- var bit = colored_bits[i],
- color = bit.getAttribute('data-color'),
- colors = color && this._handle_color(color);
-
- if ( ! colors )
- continue;
-
- bit.style.color = (bit.classList.contains('replay-color') ? cr_dark : is_dark) ? colors[1] : colors[0];
- }
-}
-
-
-FFZ.prototype._handle_filter_color = function(color) {
- if (!( color instanceof RGBAColor ))
- color = RGBAColor.fromCSS(color);
-
- var light_color = color,
- dark_color = color,
- luv = color.toLUVA();
-
- if ( luv.l < this._luv_background_bright )
- light_color = luv._l(this._luv_background_bright).toRGBA();
-
- if ( luv.l > this._luv_background_dark )
- dark_color = luv._l(this._luv_background_dark).toRGBA();
-
- return [light_color, dark_color];
-}
-
-
-FFZ.prototype._handle_color = function(color) {
- if ( color instanceof RGBAColor )
- color = color.toCSS();
-
- if ( ! color )
- return null;
-
- if ( this._hex_colors.hasOwnProperty(color) )
- return this._hex_colors[color];
-
- var rgb = RGBAColor.fromCSS(color),
-
- light_color = rgb,
- dark_color = rgb;
-
- // Color Blindness Handling
- if ( this.settings.color_blind !== '0' ) {
- var new_color = rgb.daltonize(this.settings.color_blind);
- if ( ! rgb.eq(new_color) ) {
- rgb = new_color;
- light_color = dark_color = rgb;
- }
+ this.rebuildContrast();
}
+ get contrast() { return this._contrast }
+ set contrast(val) { this._contrast = val; this.rebuildContrast() }
- // Color Processing - LUV
- if ( this.settings.fix_color === 1 ) {
- var luv = rgb.toLUVA();
+ get base() { return this._base }
+ set base(val) { this._base = val; this.rebuildContrast() }
- if ( luv.l > this._luv_required_dark )
- light_color = luv._l(this._luv_required_dark).toRGBA();
+ get dark() { return this._dark }
- if ( luv.l < this._luv_required_bright )
- dark_color = luv._l(this._luv_required_bright).toRGBA();
- }
+ get mode() { return this._mode }
+ set mode(val) { this._mode = val; this.rebuildContrast() }
- // Color Processing - HSL (Deprecated)
- if ( this.settings.fix_color === 2 ) {
- var hsl = rgb.toHSLA();
+ rebuildContrast() {
+ this._cache = {};
- light_color = hsl._l(Math.min(Math.max(0, 0.7 * hsl.l), 1)).toRGBA();
- dark_color = hsl._l(Math.min(Math.max(0, 0.3 + (0.7 * hsl.l)), 1)).toRGBA();
- }
+ const base = RGBAColor.fromCSS(this._base),
+ lum = base.luminance();
+ const dark = this._dark = lum < 0.5;
- // Color Processing - HSV (Deprecated)
- if ( this.settings.fix_color === 3 ) {
- var hsv = rgb.toHSVA();
+ if ( dark ) {
+ this._luv = new XYZAColor(
+ 0,
+ (this._contrast * (base.toXYZA().y + 0.05) - 0.05),
+ 0,
+ 1
+ ).toLUVA().l;
- if ( hsv.s === 0 ) {
- // Black and White
- light_color = hsv._v(Math.min(Math.max(0.5, 0.5 * hsv.v), 1)).toRGBA();
- dark_color = hsv._v(Math.min(Math.max(0.5, 0.5 + (0.5 * hsv.v)), 1)).toRGBA();
+ this._luma = this._contrast * (base.luminance() + 0.05) - 0.05;
} else {
- light_color = RGBAColor.fromHSVA(hsv.h, Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.s)), 1), Math.min(0.7, hsv.v), hsv.a);
- dark_color = RGBAColor.fromHSVA(hsv.h, Math.min(0.7, hsv.s), Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.v)), 1), hsv.a);
+ this._luv = new XYZAColor(
+ 0,
+ (base.toXYZA().y + 0.05) / this._contrast - 0.05,
+ 0,
+ 1
+ ).toLUVA().l;
+
+ this._luma = (base.luminance() + 0.05) / this._contrast - 0.05;
}
}
+ process(color) {
+ if ( this._mode === -1 )
+ return '';
+ else if ( this._mode === 0 )
+ return color;
- // Color Processing - RGB (Deprecated)
- if ( this.settings.fix_color === 4 ) {
- var lum = rgb.luminance();
+ if ( color instanceof RGBAColor )
+ color = color.toCSS();
- if ( lum > 0.3 ) {
- var s = 127, nc = rgb;
- while(s--) {
- nc = nc.brighten(-1);
- if ( nc.luminance() <= 0.3 )
- break;
- }
+ if ( ! color )
+ return null;
- light_color = nc;
+ if ( has(this._cache, color) )
+ return this._cache[color];
+
+ let rgb = RGBAColor.fromCSS(color);
+
+ if ( this._mode === 1 ) {
+ // HSL Luma
+ const luma = rgb.luminance();
+
+ if ( this._dark ? luma < this._luma : luma > this._luma )
+ rgb = rgb.toHSLA().targetLuminance(this._luma).toRGBA();
+
+ } else if ( this._mode === 2 ) {
+ // LUV
+ const luv = rgb.toLUVA();
+ if ( this._dark ? luv.l < this._luv : luv.l > this._luv )
+ rgb = luv._l(this._luv).toRGBA();
+
+ } else if ( this._mode === 3 ) {
+ // HSL Loop (aka BTTV Style)
+ if ( this._dark )
+ while ( rgb.get_Y() < 0.5 ) {
+ const hsl = rgb.toHSLA();
+ rgb = hsl._l(Math.min(Math.max(0, 0.1 + 0.9 * hsl.l), 1)).toRGBA();
+ }
+
+ else
+ while ( rgb.get_Y() >= 0.5 ) {
+ const hsl = rgb.toHSLA();
+ rgb = hsl._l(Math.min(Math.max(0, 0.9 * hsl.l), 1)).toRGBA();
+ }
}
- if ( lum < 0.15 ) {
- var s = 127, nc = rgb;
- while(s--) {
- nc = nc.brighten();
- if ( nc.luminance() >= 0.15 )
- break;
- }
-
- dark_color = nc;
- }
+ const out = this._cache[color] = rgb.toHex();
+ return out;
}
-
-
- // Color Processing - HSL BTTV-Like
- if ( this.settings.fix_color === 5 ) {
- // BetterTTV adjusts its colors by converting them to the YIQ color space and then
- // checking the value of Y. If it isn't past the half-way threshold, it then either
- // brightens or darkens the luminosity, in HSL color space and checks again. Repeat
- // until the color is acceptible.
-
- while ( light_color.get_Y() >= 0.5 ) {
- var hsl = light_color.toHSLA();
- light_color = hsl._l(Math.min(Math.max(0, 0.9 * hsl.l), 1)).toRGBA();
- }
-
- while ( dark_color.get_Y() < 0.5 ) {
- var hsl = dark_color.toHSLA();
- dark_color = hsl._l(Math.min(Math.max(0, 0.1 + 0.9 * hsl.l), 1)).toRGBA();
- }
- }
-
-
- // Color Processing - HSL Luma
- if ( this.settings.fix_color === 6 ) {
- var lum = rgb.luminance();
-
- if ( lum > this._hslluma_required_dark )
- light_color = rgb.toHSLA().targetLuminance(this._hslluma_required_dark).toRGBA();
-
- if ( lum < this._hslluma_required_bright )
- dark_color = rgb.toHSLA().targetLuminance(this._hslluma_required_bright).toRGBA();
- }
-
- var out = this._hex_colors[color] = [light_color.toHex(), dark_color.toHex()];
- return out;
-}
+}
\ No newline at end of file
diff --git a/src/utilities/compat/apollo.js b/src/utilities/compat/apollo.js
new file mode 100644
index 00000000..a3d376f2
--- /dev/null
+++ b/src/utilities/compat/apollo.js
@@ -0,0 +1,308 @@
+'use strict';
+
+// ============================================================================
+// Apollo
+// Legendary Data Access Layer
+// ============================================================================
+
+import Module from 'utilities/module';
+import {has} from 'utilities/object';
+
+export default class Apollo extends Module {
+ constructor(...args) {
+ super(...args);
+
+ this.modifiers = {};
+ this.post_modifiers = {};
+
+ this.inject('..web_munch');
+ this.inject('..fine');
+
+ this.registerModifier('ChannelPage_ChannelInfoBar_User', `query {
+ user {
+ stream {
+ createdAt
+ type
+ }
+ }
+}`);
+
+ this.registerModifier('FollowedIndex_CurrentUser', `query {
+ currentUser {
+ followedLiveUsers {
+ nodes {
+ profileImageURL(width: 70)
+ stream {
+ createdAt
+ }
+ }
+ }
+ followedHosts {
+ nodes {
+ hosting {
+ profileImageURL(width: 70)
+ stream {
+ createdAt
+ type
+ }
+ }
+ }
+ }
+ }
+}`);
+
+ this.registerModifier('FollowingLive_CurrentUser', `query {
+ currentUser {
+ followedLiveUsers {
+ nodes {
+ profileImageURL(width: 70)
+ stream {
+ createdAt
+ }
+ }
+ }
+ }
+}`);
+
+ this.registerModifier('ViewerCard', `query {
+ targetUser: user {
+ createdAt
+ profileViewCount
+ }
+}`);
+
+ this.registerModifier('GamePage_Game', `query {
+ game {
+ streams {
+ edges {
+ node {
+ createdAt
+ type
+ broadcaster {
+ profileImageURL(width: 70)
+ }
+ }
+ }
+ }
+ }
+}`);
+
+ }
+
+ async onEnable() {
+ // TODO: Come up with a better way to await something existing.
+ let client = this.client,
+ graphql = this.graphql;
+
+ if ( ! client ) {
+ const root = this.fine.getParent(this.fine.react),
+ ctx = root && root._context;
+
+ client = this.client = ctx && ctx.client;
+ }
+
+ if ( ! graphql )
+ graphql = this.graphql = await this.web_munch.findModule('graphql', m => m.parse && m.parseValue);
+
+ if ( ! client || ! graphql )
+ return new Promise(s => setTimeout(s,50)).then(() => this.onEnable());
+
+ // Parse the queries for modifiers that were already registered.
+ for(const key in this.modifiers)
+ if ( has(this.modifiers, key) ) {
+ const modifiers = this.modifiers[key];
+ if ( modifiers )
+ for(const mod of modifiers) {
+ if ( typeof mod === 'function' || mod[1] === false )
+ continue;
+
+ try {
+ mod[1] = graphql.parse(mod[0], {noLocation: true});
+ } catch(err) {
+ this.log.error(`Error parsing GraphQL statement for "${key}" modifier.`, err);
+ mod[1] = false;
+ }
+ }
+ }
+
+ // Register middleware so that we can intercept requests.
+ this.client.networkInterface.use([{
+ applyBatchMiddleware: (req, next) => {
+ if ( this.enabled )
+ this.apolloPreFlight(req);
+
+ next();
+ }
+ }]);
+
+ this.client.networkInterface.useAfter([{
+ applyBatchAfterware: (resp, next) => {
+ if ( this.enabled )
+ this.apolloPostFlight(resp);
+
+ next();
+ }
+ }]);
+ }
+
+
+ onDisable() {
+ // TODO: Remove Apollo middleware.
+
+ // Tear down the parsed queries.
+ for(const key in this.modifiers)
+ if ( has(this.modifiers, key) ) {
+ const modifiers = this.modifiers[key];
+ if ( modifiers )
+ for(const mod of modifiers) {
+ if ( typeof mod === 'function' )
+ continue;
+
+ mod[1] = null;
+ }
+ }
+
+ // And finally, remove our references.
+ this.client = this.graphql = null;
+ }
+
+
+ apolloPreFlight(request) {
+ for(const req of request.requests) {
+ const operation = req.operationName,
+ modifiers = this.modifiers[operation];
+
+ if ( modifiers )
+ for(const mod of modifiers) {
+ if ( typeof mod === 'function' )
+ mod(req);
+ else if ( mod[1] )
+ this.applyModifier(req, mod[1]);
+ }
+
+ this.emit(`:request.${operation}`, req.query, req.variables);
+ }
+ }
+
+ apolloPostFlight(response) {
+ for(const resp of response.responses) {
+ const operation = resp.extensions.operationName,
+ modifiers = this.post_modifiers[operation];
+
+ if ( modifiers )
+ for(const mod of modifiers)
+ mod(resp);
+
+ this.emit(`:response.${operation}`, resp.data);
+ }
+ }
+
+
+ applyModifier(request, modifier) { // eslint-disable-line class-methods-use-this
+ request.query = merge(request.query, modifier);
+ }
+
+
+ registerModifier(operation, modifier) {
+ if ( typeof modifier !== 'function' ) {
+ let parsed;
+ try {
+ parsed = this.graphql ? this.graphql.parse(modifier, {noLocation: true}) : null;
+ } catch(err) {
+ this.log.error(`Error parsing GraphQL statement for "${operation}" modifier.`, err);
+ parsed = false;
+ }
+
+ modifier = [modifier, parsed];
+ }
+
+ const mods = this.modifiers[operation] = this.modifiers[operation] || [];
+ mods.push(modifier);
+ }
+
+ unregisterModifier(operation, modifier) {
+ const mods = this.modifiers[operation];
+ if ( ! mods )
+ return;
+
+ for(let i=0; i < mods.length; i++) {
+ const mod = mods[i];
+ if ( typeof mod === 'function' ? mod === modifier : mod[0] === modifier ) {
+ mods.splice(i, 1);
+ return;
+ }
+ }
+ }
+
+
+ // ========================================================================
+ // Querying
+ // ========================================================================
+
+ getQuery(operation) {
+ const qm = this.client.queryManager,
+ name_map = qm && qm.queryIdsByName,
+ query_map = qm && qm.observableQueries,
+ query_id = name_map && name_map[operation],
+ query = query_map && query_map[query_id];
+
+ return query && query.observableQuery;
+ }
+
+}
+
+
+// ============================================================================
+// Query Merging
+// ============================================================================
+
+function canMerge(a, b) {
+ return a.kind === b.kind &&
+ a.kind !== 'FragmentDefinition' &&
+ (a.selectionSet == null) === (b.selectionSet == null);
+}
+
+
+function merge(a, b) {
+ if ( ! canMerge(a, b) )
+ return a;
+
+ if ( a.definitions ) {
+ const a_def = a.definitions,
+ b_def = b.definitions;
+
+ for(let i=0; i < a_def.length && i < b_def.length; i++)
+ a_def[i] = merge(a_def[i], b_def[i]);
+ }
+
+ if ( a.selectionSet ) {
+ const s = a.selectionSet.selections,
+ selects = {};
+ for(const sel of b.selectionSet.selections)
+ selects[`${sel.name.value}:${sel.alias?sel.alias.value:null}`] = sel;
+
+ for(let i=0, l = s.length; i < l; i++) {
+ const sel = s[i],
+ name = sel.name.value,
+ alias = sel.alias ? sel.alias.value : null,
+ key = `${name}:${alias}`,
+ other = selects[key];
+
+ if ( other ) {
+ s[i] = merge(sel, other);
+ selects[key] = null;
+ }
+ }
+
+ for(const key in selects)
+ if ( has(selects, key) ) {
+ const val = selects[key];
+ if ( val )
+ s.push(val);
+ }
+ }
+
+ // TODO: Variables?
+
+ return a;
+}
\ No newline at end of file
diff --git a/src/utilities/compat/fine-router.js b/src/utilities/compat/fine-router.js
new file mode 100644
index 00000000..a04468a0
--- /dev/null
+++ b/src/utilities/compat/fine-router.js
@@ -0,0 +1,87 @@
+'use strict';
+
+// ============================================================================
+// Fine Router
+// ============================================================================
+
+import {parse, tokensToRegExp, tokensToFunction} from 'path-to-regexp';
+import Module from 'utilities/module';
+import {has} from 'utilities/object';
+
+
+export default class FineRouter extends Module {
+ constructor(...args) {
+ super(...args);
+ this.inject('..fine');
+
+ this.__routes = [];
+ this.routes = {};
+ this.current = null;
+ this.match = null;
+ this.location = null;
+ }
+
+ onEnable() {
+ const root = this.fine.getParent(this.fine.react),
+ ctx = this.context = root && root._context,
+ router = ctx && ctx.router,
+ history = router && router.history;
+
+ if ( ! history )
+ return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
+
+ history.listen(location => {
+ if ( this.enabled )
+ this._navigateTo(location);
+ });
+
+ this._navigateTo(history.location);
+ }
+
+ _navigateTo(location) {
+ this.log.debug('New Location', location);
+ const path = location.pathname;
+ if ( path === this.location )
+ return;
+
+ this.location = path;
+
+ for(const route of this.__routes) {
+ const match = route.regex.exec(path);
+ if ( match ) {
+ this.log.debug('Matching Route', route, match);
+ this.current = route;
+ this.match = match;
+ this.emit(':route', route, match);
+ this.emit(`:route:${route.name}`, ...match);
+ return;
+ }
+ }
+
+ this.current = this.match = null;
+ this.emit(':route', null, null);
+ }
+
+ route(name, path) {
+ if ( typeof name === 'object' ) {
+ for(const key in name)
+ if ( has(name, key) )
+ this.route(key, name[key]);
+
+ return;
+ }
+
+ const parts = parse(path),
+ score = parts.length,
+ route = this.routes[name] = {
+ name,
+ parts,
+ score,
+ regex: tokensToRegExp(parts),
+ url: tokensToFunction(parts)
+ }
+
+ this.__routes.push(route);
+ this.__routes.sort(r => r.score);
+ }
+}
\ No newline at end of file
diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js
new file mode 100644
index 00000000..1379d189
--- /dev/null
+++ b/src/utilities/compat/fine.js
@@ -0,0 +1,486 @@
+'use strict';
+
+// ============================================================================
+// Fine Lib
+// It controls React.
+// ============================================================================
+
+import {EventEmitter} from 'utilities/events';
+import Module from 'utilities/module';
+import {has} from 'utilities/object';
+
+
+export default class Fine extends Module {
+ constructor(...args) {
+ super(...args);
+
+ this._wrappers = new Map;
+ this._known_classes = new Map;
+ this._observer = null;
+ this._waiting = null;
+ }
+
+
+ async onEnable() {
+ // TODO: Move awaitElement to utilities/dom
+ if ( ! this.root_element )
+ this.root_element = await this.parent.awaitElement(this.selector || '[data-reactroot]');
+
+ const accessor = this.accessor = Fine.findAccessor(this.root_element);
+ if ( ! accessor )
+ return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
+
+ this.react = this.getReactInstance(this.root_element);
+ }
+
+ onDisable() {
+ this.root_element = this.react = this.accessor = null;
+ }
+
+
+ static findAccessor(element) {
+ for(const key in element)
+ if ( key.startsWith('__reactInternalInstance$') )
+ return key;
+ }
+
+
+ // ========================================================================
+ // Low Level Accessors
+ // ========================================================================
+
+ getReactInstance(element) {
+ return element[this.accessor];
+ }
+
+ getOwner(instance) {
+ if ( instance._reactInternalInstance )
+ instance = instance._reactInternalInstance;
+ else if ( instance instanceof Node )
+ instance = this.getReactInstance(instance);
+
+ if ( ! instance )
+ return null;
+
+ return instance._owner || (instance._currentElement && instance._currentElement._owner);
+ }
+
+ getHostNode(instance) { //eslint-disable-line class-methods-use-this
+ if ( instance._reactInternalInstance )
+ instance = instance._reactInternalInstance;
+ else if ( instance instanceof Node )
+ instance = this.getReactInstance(instance);
+
+ while( instance )
+ if ( instance._hostNode )
+ return instance._hostNode;
+ else
+ instance = instance._renderedComponent;
+ }
+
+ getParent(instance) {
+ const owner = this.getOwner(instance);
+ return owner && this.getOwner(owner);
+ }
+
+ searchParent(node, criteria, max_depth=15, depth=0) {
+ if ( node._reactInternalInstance )
+ node = node._reactInternalInstance;
+ else if ( node instanceof Node )
+ node = this.getReactInstance(node);
+
+ if ( ! node || depth > max_depth )
+ return null;
+
+ const inst = node._instance;
+ if ( inst && criteria(inst) )
+ return inst;
+
+ if ( node._currentElement && node._currentElement._owner ) {
+ const result = this.searchParent(node._currentElement._owner, criteria, max_depth, depth+1);
+ if ( result )
+ return result;
+ }
+
+ if ( node._hostParent )
+ return this.searchParent(node._hostParent, criteria, max_depth, depth+1);
+
+ return null;
+ }
+
+ searchTree(node, criteria, max_depth=15, depth=0) {
+ if ( ! node )
+ node = this.react;
+ else if ( node._reactInternalInstance )
+ node = node._reactInternalInstance;
+ else if ( node instanceof Node )
+ node = this.getReactInstance(node);
+
+ if ( ! node || depth > max_depth )
+ return null;
+
+ const inst = node._instance;
+ if ( inst && criteria(inst) )
+ return inst;
+
+ const children = node._renderedChildren,
+ component = node._renderedComponent;
+
+ if ( children )
+ for(const key in children)
+ if ( has(children, key) ) {
+ const child = children[key];
+ const result = child && this.searchTree(child, criteria, max_depth, depth+1);
+ if ( result )
+ return result;
+ }
+
+ if ( component )
+ return this.searchTree(component, criteria, max_depth, depth+1);
+ }
+
+
+ searchAll(node, criterias, max_depth=15, depth=0, data) {
+ if ( ! node )
+ node = this.react;
+ else if ( node._reactInternalInstance )
+ node = node._reactInternalInstance;
+ else if ( node instanceof Node )
+ node = this.getReactInstance(node);
+
+ if ( ! data )
+ data = {
+ seen: new Set,
+ classes: criterias.map(() => null),
+ out: criterias.map(() => ({
+ cls: null, instances: new Set, depth: null
+ })),
+ max_depth: depth
+ };
+
+ if ( ! node || depth > max_depth )
+ return data.out;
+
+ if ( depth > data.max_depth )
+ data.max_depth = depth;
+
+ const inst = node._instance;
+ if ( inst ) {
+ const cls = inst.constructor,
+ idx = data.classes.indexOf(cls);
+
+ if ( idx !== -1 )
+ data.out[idx].instances.add(inst);
+
+ else if ( ! data.seen.has(cls) ) {
+ let i = criterias.length;
+ while(i-- > 0)
+ if ( criterias[i](inst) ) {
+ data.classes[i] = data.out[i].cls = cls;
+ data.out[i].instances.add(inst);
+ data.out[i].depth = depth;
+ break;
+ }
+
+ data.seen.add(cls);
+ }
+ }
+
+ const children = node._renderedChildren,
+ component = node._renderedComponent;
+
+ if ( children )
+ for(const key in children)
+ if ( has(children, key) ) {
+ const child = children[key];
+ child && this.searchAll(child, criterias, max_depth, depth+1, data);
+ }
+
+ if ( component )
+ this.searchAll(component, criterias, max_depth, depth+1, data);
+
+ return data.out;
+ }
+
+
+ // ========================================================================
+ // Class Wrapping
+ // ========================================================================
+
+ define(key, criteria) {
+ if ( this._wrappers.has(key) )
+ return this._wrappers.get(key);
+
+ if ( ! criteria )
+ throw new Error('cannot find definition and no criteria provided');
+
+ const wrapper = new FineWrapper(key, criteria, this);
+ this._wrappers.set(key, wrapper);
+
+ const data = this.searchAll(this.react, [criteria], 1000)[0];
+ if ( data.cls ) {
+ wrapper._set(data.cls, data.instances);
+ this._known_classes.set(data.cls, wrapper);
+
+ } else {
+ if ( ! this._waiting )
+ this._startWaiting();
+
+ this._waiting.push(wrapper);
+ this._waiting_crit.push(criteria);
+ }
+
+ return wrapper;
+ }
+
+
+ _checkWaiters(nodes) {
+ if ( ! this._waiting )
+ return;
+
+ if ( ! Array.isArray(nodes) )
+ nodes = [nodes];
+
+ for(let node of nodes) {
+ if ( ! node )
+ node = this.react;
+ else if ( node._reactInternalInstance )
+ node = node._reactInternalInstance;
+ else if ( node instanceof Node )
+ node = this.getReactInstance(node);
+
+ if ( ! node || ! this._waiting.length )
+ continue;
+
+ const data = this.searchAll(node, this._waiting_crit, 1000);
+ let i = data.length;
+ while(i-- > 0) {
+ if ( data[i].cls ) {
+ const d = data[i],
+ w = this._waiting.splice(i, 1)[0];
+
+ this._waiting_crit.splice(i, 1);
+ this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d);
+
+ w._set(d.cls, d.instances);
+ }
+ }
+ }
+
+ if ( ! this._waiting.length )
+ this._stopWaiting();
+ }
+
+
+ _startWaiting() {
+ this.log.info('Installing MutationObserver.');
+
+ this._waiting = [];
+ this._waiting_crit = [];
+ this._waiting_timer = setInterval(() => this._checkWaiters(), 500);
+
+ if ( ! this._observer )
+ this._observer = new MutationObserver(mutations =>
+ this._checkWaiters(mutations.map(x => x.target))
+ );
+
+ this._observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+ }
+
+
+ _stopWaiting() {
+ this.log.info('Stopping MutationObserver.');
+
+ if ( this._observer )
+ this._observer.disconnect();
+
+ if ( this._waiting_timer )
+ clearInterval(this._waiting_timer);
+
+ this._waiting = null;
+ this._waiting_crit = null;
+ this._waiting_timer = null;
+ }
+}
+
+
+
+const EVENTS = {
+ 'will-mount': 'componentWillMount',
+ mount: 'componentDidMount',
+ render: 'render',
+ 'receive-props': 'componentWillReceiveProps',
+ 'should-update': 'shouldComponentUpdate',
+ 'will-update': 'componentWillUpdate',
+ update: 'componentDidUpdate',
+ unmount: 'componentWillUnmount'
+}
+
+
+export class FineWrapper extends EventEmitter {
+ constructor(name, criteria, fine) {
+ super();
+
+ this.name = name;
+ this.criteria = criteria;
+ this.fine = fine;
+
+ this.instances = new Set;
+
+ this._wrapped = new Set;
+ this._class = null;
+ }
+
+ ready(fn) {
+ if ( this._class )
+ fn(this._class, this.instances);
+ else
+ this.once('set', fn);
+ }
+
+ _set(cls, instances) {
+ if ( this._class )
+ throw new Error('already have a class');
+
+ this._class = cls;
+ this._wrapped.add('componentWillMount');
+ this._wrapped.add('componentWillUnmount');
+
+ const t = this,
+ _instances = this.instances,
+ proto = cls.prototype,
+ o_mount = proto.componentWillMount,
+ o_unmount = proto.componentWillUnmount,
+
+ mount = proto.componentWillMount = o_mount ?
+ function(...args) {
+ this._ffz_mounted = true;
+ _instances.add(this);
+ t.emit('will-mount', this, ...args);
+ return o_mount.apply(this, args);
+ } :
+ function(...args) {
+ this._ffz_mounted = true;
+ _instances.add(this);
+ t.emit('will-mount', this, ...args);
+ },
+
+ unmount = proto.componentWillUnmount = o_unmount ?
+ function(...args) {
+ t.emit('unmount', this, ...args);
+ _instances.delete(this);
+ this._ffz_mounted = false;
+ return o_unmount.apply(this, args);
+ } :
+ function(...args) {
+ t.emit('unmount', this, ...args);
+ _instances.delete(this);
+ this._ffz_mounted = false;
+ };
+
+ this.__componentWillMount = [mount, o_mount];
+ this.__componentWillUnmount = [unmount, o_unmount];
+
+ for(const event of this.events())
+ this._maybeWrap(event);
+
+ if ( instances )
+ for(const inst of instances) {
+ if ( inst._reactInternalInstance && inst._reactInternalInstance._renderedComponent )
+ inst._ffz_mounted = true;
+ _instances.add(inst);
+ }
+
+ this.emit('set', cls, _instances);
+ }
+
+ _add(instances) {
+ for(const inst of instances)
+ this.instances.add(inst);
+ }
+
+
+ _maybeWrap(event) {
+ const key = EVENTS[event];
+ if ( ! this._class || ! key || this._wrapped.has(key) )
+ return;
+
+ this._wrap(event, key);
+ }
+
+ _wrap(event, key) {
+ if ( this._wrapped.has(key) )
+ return;
+
+ const t = this,
+ proto = this._class.prototype,
+ original = proto[key],
+
+ fn = proto[key] = original ?
+ function(...args) {
+ t.emit(event, this, ...args);
+ return original.apply(this, args);
+ } :
+
+ function(...args) {
+ t.emit(event, this, ...args);
+ };
+
+ this[`__${key}`] = [fn, original];
+ }
+
+ _unwrap(key) {
+ if ( ! this._wrapped.has(key) )
+ return;
+
+ const k = `__${key}`,
+ proto = this._class.prototype,
+ [fn, original] = this[k];
+
+ if ( proto[key] !== fn )
+ throw new Error('unable to unwrap -- prototype modified');
+
+ proto[key] = original;
+ this[k] = undefined;
+ this._wrapped.delete(key);
+ }
+
+
+ on(event, fn, ctx) {
+ this._maybeWrap(event);
+ return super.on(event, fn, ctx);
+ }
+
+ prependOn(event, fn, ctx) {
+ this._maybeWrap(event);
+ return super.prependOn(event, fn, ctx);
+ }
+
+ once(event, fn, ctx) {
+ this._maybeWrap(event);
+ return super.once(event, fn, ctx);
+ }
+
+ prependOnce(event, fn, ctx) {
+ this._maybeWrap(event);
+ return super.prependOnce(event, fn, ctx);
+ }
+
+ many(event, ttl, fn, ctx) {
+ this._maybeWrap(event);
+ return super.many(event, ttl, fn, ctx);
+ }
+
+ prependMany(event, ttl, fn, ctx) {
+ this._maybeWrap(event);
+ return super.prependMany(event, ttl, fn, ctx);
+ }
+
+ waitFor(event) {
+ this._maybeWrap(event);
+ return super.waitFor(event);
+ }
+}
\ No newline at end of file
diff --git a/src/utilities/compat/webmunch.js b/src/utilities/compat/webmunch.js
new file mode 100644
index 00000000..3c18ef00
--- /dev/null
+++ b/src/utilities/compat/webmunch.js
@@ -0,0 +1,168 @@
+'use strict';
+
+// ============================================================================
+// WebMunch
+// It consumes webpack.
+// ============================================================================
+
+import Module from 'utilities/module';
+import {has} from 'utilities/object';
+
+
+let last_muncher = 0;
+
+export default class WebMunch extends Module {
+ constructor(...args) {
+ super(...args);
+
+ this._id = `_ffz$${last_muncher++}`;
+ this._rid = 0;
+ this._original_loader = null;
+ this._known_rules = {};
+ this._require = null;
+ this._module_names = {};
+ this._mod_cache = {};
+
+ this.hookLoader();
+ this.hookRequire();
+ }
+
+
+ // ========================================================================
+ // Loaded Modules
+ // ========================================================================
+
+ hookLoader(attempts) {
+ if ( this._original_loader )
+ return this.log.warn('Attempted to call hookLoader twice.');
+
+ this._original_loader = window.webpackJsonp;
+ if ( ! this._original_loader ) {
+ if ( attempts > 500 )
+ return this.log.error("Unable to find webpack's loader after two minutes.");
+
+ return setTimeout(this.hookLoader.bind(this, (attempts||0) + 1), 250);
+ }
+
+ this.log.info(`Found and wrapped webpack's loader after ${(attempts||0)*250}ms.`);
+ window.webpackJsonp = this.webpackJsonp.bind(this);
+ }
+
+ webpackJsonp(chunk_ids, modules) {
+ const names = chunk_ids.map(x => this._module_names[x] || x).join(', ');
+ this.log.info(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
+ this.log.debug(`Modules: ${Object.keys(modules)}`);
+
+ const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params
+
+ this.emit(':loaded', chunk_ids, names, modules);
+
+ return res;
+ }
+
+
+ // ========================================================================
+ // Finding Modules
+ // ========================================================================
+
+ known(key, predicate) {
+ if ( typeof key === 'object' ) {
+ for(const k in key)
+ if ( has(key, k) )
+ this.known(k, key[k]);
+
+ return;
+ }
+
+ this._known_rules[key] = predicate;
+ }
+
+
+ async findModule(key, predicate) {
+ if ( ! this._require )
+ await this.getRequire();
+
+ return this.getModule(key, predicate);
+ }
+
+
+ getModule(key, predicate) {
+ if ( typeof key === 'function' ) {
+ predicate = key;
+ key = null;
+ }
+
+ if ( key && this._mod_cache[key] )
+ return this._mod_cache[key];
+
+ const require = this._require;
+ if ( ! require || ! require.c )
+ return null;
+
+ if ( ! predicate )
+ predicate = this._known_rules[key];
+
+ if ( ! predicate )
+ throw new Error(`no known predicate for locating ${key}`);
+
+ for(const k in require.c)
+ if ( has(require.c, k) ) {
+ const module = require.c[k],
+ mod = module && module.exports;
+
+ if ( mod && predicate(mod) ) {
+ if ( key )
+ this._mod_cache[key] = mod;
+ return mod;
+ }
+ }
+ }
+
+
+ // ========================================================================
+ // Grabbing Require
+ // ========================================================================
+
+ getRequire() {
+ if ( this._require )
+ return Promise.resolve(this._require);
+
+ return new Promise(resolve => {
+ const id = `${this._id}$${this._rid++}`;
+ (this._original_loader || window.webpackJsonp)(
+ [],
+ {
+ [id]: (module, exports, __webpack_require__) => {
+ resolve(this._require = __webpack_require__);
+ }
+ },
+ [id]
+ )
+ });
+ }
+
+ async hookRequire() {
+ const start_time = performance.now(),
+ require = await this.getRequire(),
+ time = performance.now() - start_time;
+
+ this.log.info(`require() grabbed in ${time.toFixed(5)}ms.`);
+
+ const loader = require.e && require.e.toString();
+ let modules;
+ if ( loader && loader.indexOf('Loading chunk') !== -1 ) {
+ const data = /({0:.*?})/.exec(loader);
+ if ( data )
+ try {
+ modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
+ } catch(err) { } // eslint-disable-line no-empty
+ }
+
+ if ( modules ) {
+ this._module_names = modules;
+ this.log.info(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
+ } else
+ this.log.warn(`Unable to find chunk names in require().`);
+ }
+
+}
\ No newline at end of file
diff --git a/src/utilities/constants.js b/src/utilities/constants.js
new file mode 100644
index 00000000..378865b4
--- /dev/null
+++ b/src/utilities/constants.js
@@ -0,0 +1,22 @@
+'use strict';
+
+export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev');
+export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com';
+
+export const API_SERVER = '//api.frankerfacez.com';
+
+export const WS_CLUSTERS = {
+ Production: [
+ ['wss://catbag.frankerfacez.com/', 0.25],
+ ['wss://andknuckles.frankerfacez.com/', 1],
+ ['wss://tuturu.frankerfacez.com/', 1]
+ ],
+
+ Development: [
+ ['wss://127.0.0.1:8003/', 1]
+ ]
+}
+
+export const IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent);
+export const IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== -1 : /Windows/.test(navigator.userAgent);
+export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1;
\ No newline at end of file
diff --git a/src/utilities/dom.js b/src/utilities/dom.js
new file mode 100644
index 00000000..854e1cca
--- /dev/null
+++ b/src/utilities/dom.js
@@ -0,0 +1,149 @@
+'use strict';
+
+import {has} from 'utilities/object';
+
+const ATTRS = [
+ 'accept', 'accept-charset', 'accesskey', 'action', 'align', 'alt', 'async',
+ 'autocomplete', 'autofocus', 'autoplay', 'bgcolor', 'border', 'buffered',
+ 'challenge', 'charset', 'checked', 'cite', 'class', 'code', 'codebase',
+ 'color', 'cols', 'colspan', 'content', 'contenteditable', 'contextmenu',
+ 'controls', 'coords', 'crossorigin', 'data', 'data-*', 'datetime',
+ 'default', 'defer', 'dir', 'dirname', 'disabled', 'download', 'draggable',
+ 'dropzone', 'enctype', 'for', 'form', 'formaction', 'headers', 'height',
+ 'hidden', 'high', 'href', 'hreflang', 'http-equiv', 'icon', 'id',
+ 'integrity', 'ismap', 'itemprop', 'keytype', 'kind', 'label', 'lang',
+ 'language', 'list', 'loop', 'low', 'manifest', 'max', 'maxlength',
+ 'minlength', 'media', 'method', 'min', 'multiple', 'muted', 'name',
+ 'novalidate', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster',
+ 'preload', 'radiogroup', 'readonly', 'rel', 'required', 'reversed', 'rows',
+ 'rowspan', 'sandbox', 'scope', 'scoped', 'seamless', 'selected', 'shape',
+ 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src', 'srcdoc', 'srclang',
+ 'srcset', 'start', 'step', 'style', 'summary', 'tabindex', 'target',
+ 'title', 'type', 'usemap', 'value', 'width', 'wrap'
+];
+
+
+const range = document.createRange();
+
+function camelCase(name) {
+ return name.replace(/[-_]\w/g, m => m[1].toUpperCase());
+}
+
+
+export function createElement(tag, props, children, no_sanitize) {
+ const el = document.createElement(tag);
+
+ if ( typeof props === 'string' )
+ el.className = props;
+ else if ( props )
+ for(const key in props)
+ if ( has(props, key) ) {
+ const lk = key.toLowerCase(),
+ prop = props[key];
+
+ if ( lk === 'style' ) {
+ if ( typeof prop === 'string' )
+ el.style.cssText = prop;
+ else
+ for(const k in prop)
+ if ( has(prop, k) )
+ el.style[k] = prop[k];
+
+ } else if ( lk === 'dataset' ) {
+ for(const k in prop)
+ if ( has(prop, k) )
+ el.dataset[camelCase(k)] = prop[k];
+
+ } else if ( key === 'dangerouslySetInnerHTML' ) {
+ // React compatibility is cool. SeemsGood
+ if ( prop && prop.__html )
+ el.innerHTML = prop.__html;
+
+ } else if ( lk.startsWith('on') )
+ el.addEventListener(lk.slice(2), prop);
+
+ else if ( lk.startsWith('data-') )
+ el.dataset[camelCase(lk.slice(5))] = prop;
+
+ else if ( lk.startsWith('aria-') || ATTRS.includes(lk) )
+ el.setAttribute(key, prop);
+
+ else
+ el[key] = props[key];
+ }
+
+ if ( children )
+ setChildren(el, children, no_sanitize);
+
+ return el;
+}
+
+export function setChildren(el, children, no_sanitize) {
+ if ( typeof children === 'string' ) {
+ if ( no_sanitize )
+ el.innerHTML = children;
+ else
+ el.textContent = children;
+
+ } else if ( Array.isArray(children) ) {
+ for(const child of children)
+ if ( typeof child === 'string' )
+ el.appendChild(no_sanitize ?
+ range.createContextualFragment(child) :
+ document.createTextNode(child)
+ );
+
+ else if ( child )
+ el.appendChild(child);
+
+ } else if ( children )
+ el.appendChild(children);
+}
+
+
+const el = createElement('span');
+
+export function sanitize(text) {
+ el.textContent = text;
+ return el.innerHTML;
+}
+
+
+let last_id = 0;
+
+export class ManagedStyle {
+ constructor(id) {
+ this.id = id || last_id++;
+
+ this._blocks = {};
+
+ this._style = createElement('style', {
+ type: 'text/css',
+ id: `ffz--managed-style--${this.id}`
+ });
+
+ document.head.appendChild(this._style);
+ }
+
+ destroy() {
+ document.head.removeChild(this._style);
+ this._blocks = null;
+ this._style = null;
+ }
+
+ set(key, value) {
+ let block = this._blocks[key];
+ if ( block )
+ block.textContent = value;
+ else
+ this._style.appendChild(this._blocks[key] = document.createTextNode(value));
+ }
+
+ delete(key) {
+ const block = this._blocks[key];
+ if ( block ) {
+ this._style.removeChild(block);
+ this._blocks[key] = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/utilities/events.js b/src/utilities/events.js
new file mode 100644
index 00000000..27a86563
--- /dev/null
+++ b/src/utilities/events.js
@@ -0,0 +1,313 @@
+// ============================================================================
+// EventEmitter
+// Homegrown for that lean feeling.
+// ============================================================================
+
+import {has} from 'utilities/object';
+
+const Detach = {};
+
+const SNAKE_CAPS = /([a-z])([A-Z])/g,
+ SNAKE_SPACE = /[ \t\W]/g,
+ SNAKE_TRIM = /^_+|_+$/g;
+
+
+String.prototype.toSnakeCase = function() {
+ return this
+ .replace(SNAKE_CAPS, '$1_$2')
+ .replace(SNAKE_SPACE, '_')
+ .replace(SNAKE_TRIM, '')
+ .toLowerCase();
+}
+
+
+export class EventEmitter {
+ constructor() {
+ this.__listeners = {};
+ this.__dead_events = 0;
+ }
+
+ __cleanListeners() {
+ if ( ! this.__dead_events )
+ return;
+
+ const nl = {}, ol = this.__listeners;
+ for(const key in ol)
+ if ( has(ol, key) ) {
+ const val = ol[key];
+ if ( val )
+ nl[key] = val;
+ }
+
+ this.__listeners = nl;
+ this.__dead_events = 0;
+ }
+
+
+ // ========================================================================
+ // Public Methods
+ // ========================================================================
+
+ on(event, fn, ctx) {
+ if ( typeof fn !== 'function' )
+ throw new TypeError('fn must be a function');
+
+ (this.__listeners[event] = this.__listeners[event] || []).push([fn, ctx, false])
+ }
+
+ prependOn(event, fn, ctx) {
+ if ( typeof fn !== 'function' )
+ throw new TypeError('fn must be a function');
+
+ (this.__listeners[event] = this.__listeners[event] || []).unshift([fn, ctx, false])
+ }
+
+ once(event, fn, ctx) { return this.many(event, 1, fn, ctx) }
+ prependOnce(event, fn, ctx) { return this.prependMany(event, 1, fn, ctx) }
+
+ many(event, ttl, fn, ctx) {
+ if ( typeof fn !== 'function' )
+ throw new TypeError('fn must be a function');
+
+ if ( typeof ttl !== 'number' || isNaN(ttl) || ! isFinite(ttl) || ttl < 1 )
+ throw new TypeError('ttl must be a positive, finite number');
+
+ (this.__listeners[event] = this.__listeners[event] || []).push([fn, ctx, ttl]);
+ }
+
+ prependMany(event, ttl, fn, ctx) {
+ if ( typeof fn !== 'function' )
+ throw new TypeError('fn must be a function');
+
+ if ( typeof ttl !== 'number' || isNaN(ttl) || ! isFinite(ttl) || ttl < 1 )
+ throw new TypeError('ttl must be a positive, finite number');
+
+ (this.__listeners[event] = this.__listeners[event] || []).unshift([fn, ctx, ttl]);
+ }
+
+ waitFor(event) {
+ return new Promise(resolve => {
+ (this.__listeners[event] = this.__listeners[event] || []).push([resolve, null, 1]);
+ })
+ }
+
+ off(event, fn, ctx) {
+ let list = this.__listeners[event];
+ if ( ! list )
+ return;
+
+ if ( ! fn )
+ list = null;
+ else {
+ list = list.filter(([f, c]) => !(f === fn && (!ctx || ctx === c)));
+ if ( ! list.length )
+ list = null;
+ }
+
+ this.__listeners[event] = list;
+ if ( ! list )
+ this.__dead_events++;
+ }
+
+ events() {
+ this.__cleanListeners();
+ return Object.keys(this.__listeners);
+ }
+
+ listeners(event) {
+ const list = this.__listeners[event];
+ return list ? Array.from(list) : [];
+ }
+
+ emit(event, ...args) {
+ const list = this.__listeners[event];
+ if ( ! list )
+ return;
+
+ // Track removals separately to make iteration over the event list
+ // much, much simpler.
+ const removed = new Set;
+
+ for(const item of list) {
+ const [fn, ctx, ttl] = item,
+ ret = fn.apply(ctx, args);
+
+ if ( ret === Detach )
+ removed.add(item);
+ else if ( ttl !== false ) {
+ if ( ttl <= 1 )
+ removed.add(item);
+ else
+ item[2] = ttl - 1;
+ }
+ }
+
+ if ( removed.size ) {
+ // Re-grab the list to make sure it wasn't removed mid-iteration.
+ const new_list = this.__listeners[event];
+ if ( new_list ) {
+ for(const item of removed) {
+ const idx = new_list.indexOf(item);
+ if ( idx !== -1 )
+ new_list.splice(idx, 1);
+ }
+
+ if ( ! list.length ) {
+ this.__listeners[event] = null;
+ this.__dead_events++;
+ }
+ }
+ }
+ }
+
+ emitAsync(event, ...args) {
+ const list = this.__listeners[event];
+ if ( ! list )
+ return Promise.resolve([]);
+
+ // Track removals separately to make iteration over the event list
+ // much, much simpler.
+ const removed = new Set,
+ promises = [];
+
+ for(const item of list) {
+ const [fn, ctx, ttl] = item;
+ const ret = fn.apply(ctx, args);
+ if ( ret === Detach )
+ removed.add(item);
+ else if ( ttl !== false ) {
+ if ( ttl <= 1 )
+ removed.add(item);
+ else
+ item[2] = ttl - 1;
+ }
+
+ if ( ret !== Detach )
+ promises.push(ret);
+ }
+
+ if ( removed.size ) {
+ // Re-grab the list to make sure it wasn't removed mid-iteration.
+ const new_list = this.__listeners[event];
+ if ( new_list ) {
+ for(const item of removed) {
+ const idx = new_list.indexOf(item);
+ if ( idx !== -1 )
+ new_list.splice(idx, 1);
+ }
+
+ if ( ! list.length ) {
+ this.__listeners[event] = null;
+ this.__dead_events++;
+ }
+ }
+ }
+
+ return Promise.all(promises);
+ }
+}
+
+EventEmitter.Detach = Detach;
+
+
+export default class HierarchicalEventEmitter extends EventEmitter {
+ constructor(name, parent) {
+ super();
+
+ this.name = name || (this.constructor.name || '').toSnakeCase();
+ this.parent = parent;
+
+ if ( parent ) {
+ this.root = parent.root;
+ this.__listeners = parent.__listeners;
+ this.__path = name && parent.__path ? `${parent.__path}.${name}` : name;
+
+ } else {
+ this.root = this;
+ this.__path = undefined;
+ }
+
+ this.__path_parts = this.__path ? this.__path.split('.') : [];
+ }
+
+
+ // ========================================================================
+ // Public Properties
+ // ========================================================================
+
+ get path() {
+ return this.__path;
+ }
+
+
+ // ========================================================================
+ // Public Methods
+ // ========================================================================
+
+ abs_path(path) {
+ if ( typeof path !== 'string' || ! path.length )
+ throw new TypeError('path must be a non-empty string');
+
+ let i = 0, chr;
+ const parts = this.__path_parts,
+ depth = parts.length;
+
+ do {
+ chr = path.charAt(i);
+ if ( path.charAt(i) === '.' ) {
+ if ( i > depth )
+ throw new Error('invalid path: reached top of stack');
+ continue;
+ }
+
+ break;
+ } while ( ++i < path.length );
+
+ const event = chr === ':';
+ if ( i === 0 )
+ return event && this.__path ? `${this.__path}${path}` : path;
+
+ const prefix = parts.slice(0, depth - (i-1)).join('.'),
+ remain = path.slice(i);
+
+ if ( ! prefix.length )
+ return remain;
+
+ else if ( ! remain.length )
+ return prefix;
+
+ else if ( event )
+ return prefix + remain;
+
+ return `${prefix}.${remain}`;
+ }
+
+
+ on(event, fn, ctx) { return super.on(this.abs_path(event), fn, ctx) }
+ prependOn(event, fn, ctx) { return super.prependOn(this.abs_path(event), fn, ctx) }
+
+ once(event, fn, ctx) { return super.once(this.abs_path(event), fn, ctx) }
+ prependOnce(event, fn, ctx) { return super.prependOnce(this.abs_path(event), fn, ctx) }
+
+ many(event, ttl, fn, ctx) { return super.many(this.abs_path(event), ttl, fn, ctx) }
+ prependMany(event, ttl, fn, ctx) { return super.prependMany(this.abs_path(event), ttl, fn, ctx) }
+
+ waitFor(event) { return super.waitFor(this.abs_path(event)) }
+ off(event, fn, ctx) { return super.off(this.abs_path(event), fn, ctx) }
+ listeners(event) { return super.listeners(this.abs_path(event)) }
+
+ emit(event, ...args) { return super.emit(this.abs_path(event), ...args) }
+ emitAsync(event, ...args) { return super.emitAsync(this.abs_path(event), ...args) }
+
+ events(include_children) {
+ this.__cleanListeners();
+ const keys = Object.keys(this.__listeners),
+ path = this.__path || '',
+ len = path.length;
+
+ return keys.filter(x => {
+ const y = x.charAt(len);
+ return x.startsWith(path) && (y === '' || (include_children && y === '.') || y === ':');
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/utilities/filtering.js b/src/utilities/filtering.js
new file mode 100644
index 00000000..f68a3d81
--- /dev/null
+++ b/src/utilities/filtering.js
@@ -0,0 +1,6 @@
+'use strict';
+
+// ============================================================================
+// Advanced Filter System
+// ============================================================================
+
diff --git a/src/utilities/logging.js b/src/utilities/logging.js
new file mode 100644
index 00000000..752f3b71
--- /dev/null
+++ b/src/utilities/logging.js
@@ -0,0 +1,73 @@
+'use strict';
+
+export default class Logger {
+ constructor(parent, name, level) {
+ this.parent = parent;
+ this.name = name;
+
+ this.enabled = true;
+ this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
+
+ this.children = {};
+ }
+
+ get(name, level) {
+ if ( ! this.children[name] )
+ this.children[name] = new Logger(this, (this.name ? `${this.name}.${name}` : name), level);
+
+ return this.children[name];
+ }
+
+ debug(...args) {
+ return this.invoke(Logger.DEBUG, args);
+ }
+
+ info(...args) {
+ return this.invoke(Logger.INFO, args);
+ }
+
+ warn(...args) {
+ return this.invoke(Logger.WARN, args);
+ }
+
+ error(...args) {
+ return this.invoke(Logger.ERROR, args);
+ }
+
+ /* eslint no-console: "off" */
+ invoke(level, args) {
+ if ( ! this.enabled || level < this.level )
+ return;
+
+ const message = Array.prototype.slice.call(args);
+
+ if ( this.name )
+ message.unshift(`%cFFZ [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
+ else
+ message.unshift('%cFFZ:%c', 'color:#755000; font-weight:bold', '');
+
+ if ( level === Logger.DEBUG )
+ console.info(...message);
+
+ else if ( level === Logger.INFO )
+ console.info(...message);
+
+ else if ( level === Logger.WARN )
+ console.warn(...message);
+
+ else if ( level === Logger.ERROR )
+ console.error(...message);
+
+ else
+ console.log(...message);
+ }
+}
+
+
+Logger.DEFAULT_LEVEL = 2;
+
+Logger.DEBUG = 1;
+Logger.INFO = 2;
+Logger.WARN = 4;
+Logger.ERROR = 8;
+Logger.OFF = 99;
\ No newline at end of file
diff --git a/src/utilities/module.js b/src/utilities/module.js
new file mode 100644
index 00000000..f7a39cde
--- /dev/null
+++ b/src/utilities/module.js
@@ -0,0 +1,563 @@
+'use strict';
+
+// ============================================================================
+// Module System
+// Modules are cool.
+// ============================================================================
+
+import EventEmitter from 'utilities/events';
+import {has} from 'utilities/object';
+
+
+// ============================================================================
+// Module
+// ============================================================================
+
+export const State = {
+ UNLOADED: 0,
+ LOADING: 1,
+ LOADED: 2,
+ UNLOADING: 3,
+
+ DISABLED: 0,
+ ENABLING: 1,
+ ENABLED: 2,
+ DISABLING: 3
+}
+
+
+export default class Module extends EventEmitter {
+ constructor(name, parent) {
+ if ( ! parent && name instanceof Module ) {
+ parent = name;
+ name = null;
+ }
+
+ super(name, parent);
+ this.__modules = parent ? parent.__modules : {};
+ this.children = {};
+
+ if ( parent && ! parent.children[this.name] )
+ parent.children[this.name] = this;
+
+ if ( this.root === this )
+ this.__modules[this.__path || ''] = this;
+
+ this.__load_state = this.onLoad ? State.UNLOADED : State.LOADED;
+ this.__state = this.onLoad || this.onEnable ?
+ State.DISABLED : State.ENABLED;
+ }
+
+
+ // ========================================================================
+ // Public Properties
+ // ========================================================================
+
+ get state() { return this.__state }
+ get load_state() { return this.__load_state }
+
+ get loaded() { return this.__load_state === State.LOADED }
+ get loading() { return this.__load_state === State.LOADING }
+
+ get enabled() { return this.__state === State.ENABLED }
+ get enabling() { return this.__state === State.ENABLING }
+
+
+ get log() {
+ if ( ! this.__log )
+ this.__log = this.parent && this.parent.log.get(this.name);
+ return this.__log
+ }
+
+ set log(log) {
+ this.__log = log;
+ }
+
+
+ // ========================================================================
+ // State! Glorious State
+ // ========================================================================
+
+ load(...args) {
+ return this.__load(args, this.__path, []);
+ }
+
+ unload(...args) {
+ return this.__unload(args, this.__path, []);
+ }
+
+ enable(...args) {
+ return this.__enable(args, this.__path, []);
+ }
+
+ disable(...args) {
+ return this.__disable(args, this.__path, []);
+ }
+
+
+ __load(args, initial, chain) {
+ const path = this.__path || this.name,
+ state = this.__load_state;
+
+ if ( state === State.LOADING )
+ return this.__load_promise;
+
+ else if ( state === State.LOADED )
+ return Promise.resolve();
+
+ else if ( state === State.UNLOADING )
+ return Promise.reject(new ModuleError(`attempted to load module ${path} while module is being unloaded`));
+
+ else if ( chain.includes(this) )
+ return Promise.reject(new CyclicDependencyError(`cyclic load requirements when loading ${initial}`, chain));
+
+ chain.push(this);
+
+ this.__load_state = State.LOADING;
+ return this.__load_promise = (async () => {
+ if ( this.load_requires ) {
+ const promises = [];
+ for(const name of this.load_requires) {
+ const module = this.resolve(name);
+ if ( ! module || !(module instanceof Module) )
+ throw new ModuleError(`cannot find required module ${name} when loading ${path}`);
+
+ promises.push(module.__enable([], initial, Array.from(chain)));
+ }
+
+ await Promise.all(promises);
+ }
+
+ if ( this.onLoad )
+ return this.onLoad(...args);
+
+ })().then(ret => {
+ this.__load_state = State.LOADED;
+ this.__load_promise = null;
+ this.emit(':loaded', this);
+ return ret;
+ }).catch(err => {
+ this.__load_state = State.UNLOADED;
+ this.__load_promise = null;
+ throw err;
+ });
+ }
+
+
+ __unload(args, initial, chain) {
+ const path = this.__path || this.name,
+ state = this.__load_state;
+
+ if ( state === State.UNLOADING )
+ return this.__load_promise;
+
+ else if ( state === State.UNLOADED )
+ return Promise.resolve();
+
+ else if ( ! this.onUnload )
+ return Promise.reject(new ModuleError(`attempted to unload module ${path} but module cannot be unloaded`));
+
+ else if ( state === State.LOADING )
+ return Promise.reject(new ModuleError(`attempted to unload module ${path} while module is being loaded`));
+
+ else if ( chain.includes(this) )
+ return Promise.reject(new CyclicDependencyError(`cyclic load requirements when unloading ${initial}`, chain));
+
+ chain.push(this);
+
+ this.__load_state = State.UNLOADING;
+ return this.__load_promise = (async () => {
+ if ( this.__state !== State.DISABLED )
+ await this.disable();
+
+ if ( this.load_dependents ) {
+ const promises = [];
+ for(const name of this.load_dependents) {
+ const module = this.resolve(name);
+ if ( ! module )
+ throw new ModuleError(`cannot find depending module ${name} when unloading ${path}`);
+
+ promises.push(module.__unload([], initial, Array.from(chain)));
+ }
+
+ await Promise.all(promises);
+ }
+
+ return this.onUnload(...args);
+
+ })().then(ret => {
+ this.__load_state = State.UNLOADED;
+ this.__load_promise = null;
+ this.emit(':unloaded', this);
+ return ret;
+ }).catch(err => {
+ this.__load_state = State.LOADED;
+ this.__load_promise = null;
+ throw err;
+ });
+ }
+
+
+ __enable(args, initial, chain) {
+ const path = this.__path || this.name,
+ state = this.__state;
+
+ if ( state === State.ENABLING )
+ return this.__state_promise;
+
+ else if ( state === State.ENABLED )
+ return Promise.resolve();
+
+ else if ( state === State.DISABLING )
+ return Promise.reject(new ModuleError(`attempted to enable module ${path} while module is being disabled`));
+
+ else if ( chain.includes(this) )
+ return Promise.reject(new CyclicDependencyError(`cyclic requirements when enabling ${initial}`, chain));
+
+ chain.push(this);
+
+ this.__state = State.ENABLING;
+ return this.__state_promise = (async () => {
+ const promises = [],
+ requires = this.requires,
+ load_state = this.__load_state;
+
+ if ( load_state === State.UNLOADING )
+ // We'd abort for this later to, but kill it now before we start
+ // any unnecessary work.
+ throw new ModuleError(`attempted to load module ${path} while module is being unloaded`);
+
+ else if ( load_state === State.LOADING || load_state === State.UNLOADED )
+ promises.push(this.load());
+
+ if ( requires )
+ for(const name of requires) {
+ const module = this.resolve(name);
+ if ( ! module || !(module instanceof Module) )
+ throw new ModuleError(`cannot find required module ${name} when enabling ${path}`);
+
+ promises.push(module.__enable([], initial, Array.from(chain)));
+ }
+
+ await Promise.all(promises);
+ if ( this.onEnable )
+ return this.onEnable(...args);
+
+ })().then(ret => {
+ this.__state = State.ENABLED;
+ this.__state_promise = null;
+ this.emit(':enabled', this);
+ return ret;
+
+ }).catch(err => {
+ this.__state = State.DISABLED;
+ this.__state_promise = null;
+ throw err;
+ });
+ }
+
+
+ __disable(args, initial, chain) {
+ const path = this.__path || this.name,
+ state = this.__state;
+
+ if ( state === State.DISABLING )
+ return this.__state_promise;
+
+ else if ( state === State.DISABLED )
+ return Promise.resolve();
+
+ else if ( ! this.onDisable )
+ return Promise.reject(new ModuleError(`attempted to disable module ${path} but module cannot be disabled`));
+
+ else if ( state === State.ENABLING )
+ return Promise.reject(new ModuleError(`attempted to disable module ${path} but module is being enabled`));
+
+ else if ( chain.includes(this) )
+ return Promise.reject(new CyclicDependencyError(`cyclic requirements when disabling ${initial}`, chain));
+
+ chain.push(this);
+
+ this.__state = State.DISABLING;
+ return this.__state_promise = (async () => {
+ if ( this.__load_state !== State.LOADED )
+ // We'd abort for this later to, but kill it now before we start
+ // any unnecessary work.
+ throw new ModuleError(`attempted to disable module ${path} but module is unloaded -- weird state`);
+
+ if ( this.dependents ) {
+ const promises = [];
+ for(const name of this.dependents) {
+ const module = this.resolve(name);
+ if ( ! module )
+ throw new ModuleError(`cannot find depending module ${name} when disabling ${path}`);
+
+ promises.push(module.__disable([], initial, Array.from(chain)));
+ }
+
+ await Promise.all(promises);
+ }
+
+ return this.onDisable(...args);
+
+ })().then(ret => {
+ this.__state = State.ENABLED;
+ this.__state_promise = null;
+ this.emit(':disabled', this);
+ return ret;
+
+ }).catch(err => {
+ this.__state = State.DISABLED;
+ this.__state_promise = null;
+ throw err;
+ });
+ }
+
+
+ // ========================================================================
+ // Slightly Easier Events
+ // ========================================================================
+
+ on(event, fn, ctx) {
+ return super.on(event, fn, ctx === undefined ? this : ctx)
+ }
+
+ prependOn(event, fn, ctx) {
+ return super.prependOn(event, fn, ctx === undefined ? this : ctx)
+ }
+
+ many(event, ttl, fn, ctx) {
+ return super.many(event, ttl, fn, ctx === undefined ? this : ctx)
+ }
+
+ prependMany(event, ttl, fn, ctx) {
+ return super.prependMany(event, ttl, fn, ctx === undefined ? this : ctx)
+ }
+
+ once(event, fn, ctx) {
+ return super.once(event, fn, ctx === undefined ? this : ctx)
+ }
+
+ prependOnce(event, fn, ctx) {
+ return super.prependOnce(event, fn, ctx === undefined ? this : ctx)
+ }
+
+ off(event, fn, ctx) {
+ return super.off(event, fn, ctx === undefined ? this : ctx)
+ }
+
+
+ // ========================================================================
+ // Child Control
+ // ========================================================================
+
+ loadModules(...names) {
+ return Promise.all(names.map(n => this.resolve(n).load()))
+ }
+
+ unloadModules(...names) {
+ return Promise.all(names.map(n => this.resolve(n).unload()))
+ }
+
+ enableModules(...names) {
+ return Promise.all(names.map(n => this.resolve(n).enable()))
+ }
+
+ disableModules(...names) {
+ return Promise.all(names.map(n => this.resolve(n).disable()))
+ }
+
+
+ // ========================================================================
+ // Module Management
+ // ========================================================================
+
+ resolve(name) {
+ if ( name instanceof Module )
+ return name;
+
+ return this.__modules[this.abs_path(name)];
+ }
+
+
+ __get_requires() {
+ if ( has(this, 'requires') )
+ return this.requires;
+ if ( has(this.constructor, 'requires') )
+ return this.constructor.requires;
+ }
+
+
+ __get_load_requires() {
+ if ( has(this, 'load_requires') )
+ return this.load_requires;
+ if ( has(this.constructor, 'load_requires') )
+ return this.constructor.load_requires;
+ }
+
+
+ inject(name, module) {
+ if ( name instanceof Module || name.prototype instanceof Module ) {
+ module = name;
+ name = null;
+ }
+
+ const requires = this.requires = this.__get_requires() || [];
+
+ if ( module instanceof Module ) {
+ // Existing Instance
+ if ( ! name )
+ name = module.constructor.name.toSnakeCase();
+
+ } else if ( module && module.prototype instanceof Module ) {
+ // New Instance
+ if ( ! name )
+ name = module.name.toSnakeCase();
+
+ module = this.register(name, module);
+
+ } else if ( name ) {
+ // Just a Name
+ const full_name = name;
+ name = name.replace(/^(?:[^.]*\.)+/, '');
+ module = this.resolve(full_name);
+
+ // Allow injecting a module that doesn't exist yet?
+
+ if ( ! module || !(module instanceof Module) ) {
+ if ( module )
+ module[2].push([this.__path, name]);
+ else
+ this.__modules[this.abs_path(full_name)] = [[], [], [[this.__path, name]]]
+
+ return this[name] = null;
+ }
+
+ } else
+ throw new TypeError(`must provide a valid module name or class`);
+
+ if ( ! module )
+ throw new Error(`cannot find module ${name} or no module provided`);
+
+ requires.push(module.abs_path('.'));
+
+ if ( this.enabled && ! module.enabled )
+ module.enable();
+
+ return this[name] = module;
+ }
+
+
+ register(name, module, inject_reference) {
+ if ( name.prototype instanceof Module ) {
+ inject_reference = module;
+ module = name;
+ name = module.name.toSnakeCase();
+ }
+
+ const path = this.abs_path(`.${name}`),
+ proto = module.prototype,
+ old_val = this.__modules[path];
+
+ if ( !(proto instanceof Module) )
+ throw new TypeError(`Module ${name} is not subclass of Module.`);
+
+ if ( old_val instanceof Module )
+ throw new ModuleError(`Name Collision for Module ${path}`);
+
+ const dependents = old_val || [[], [], []],
+ inst = this.__modules[path] = new module(name, this),
+ requires = inst.requires = inst.__get_requires() || [],
+ load_requires = inst.load_requires = inst.__get_load_requires() || [];
+
+ inst.dependents = dependents[0];
+ inst.load_dependents = dependents[1];
+
+ if ( inst instanceof SiteModule && ! requires.includes('site') )
+ requires.push('site');
+
+ for(const req_name of requires) {
+ const req_path = inst.abs_path(req_name),
+ req_mod = this.__modules[req_path];
+
+ if ( ! req_mod )
+ this.__modules[req_path] = [[path],[],[]];
+ else if ( Array.isArray(req_mod) )
+ req_mod[0].push(path);
+ else
+ req_mod.dependents.push(path);
+ }
+
+ for(const req_name of load_requires) {
+ const req_path = inst.abs_path(req_name),
+ req_mod = this.__modules[req_path];
+
+ if ( ! req_mod )
+ this.__modules[req_path] = [[], [path], []];
+ else if ( Array.isArray(req_mod) )
+ req_mod[1].push(path);
+ else
+ req_mod.load_dependents.push(path);
+ }
+
+ for(const [in_path, in_name] of dependents[2]) {
+ const in_mod = this.resolve(in_path);
+ if ( in_mod )
+ in_mod[in_name] = inst;
+ else
+ this.log.warn(`Unable to find module "${in_path}" that wanted "${in_name}".`);
+ }
+
+ if ( inject_reference )
+ this[name] = inst;
+
+ return inst;
+ }
+
+
+ populate(ctx, log) {
+ log = log || this.log;
+ const added = {};
+ for(const raw_path of ctx.keys()) {
+ const raw_module = ctx(raw_path),
+ module = raw_module.module || raw_module.default,
+ name = raw_path.slice(2, raw_path.length - (raw_path.endsWith('/index.js') ? 9 : 3));
+
+ try {
+ added[name] = this.register(name, module);
+ } catch(err) {
+ log && log.warn(err, `Skipping ${raw_path}`);
+ }
+ }
+
+ return added;
+ }
+
+}
+
+
+Module.State = State;
+Module.prototype.State = State;
+
+
+export class SiteModule extends Module {
+ constructor(name, parent) {
+ super(name, parent);
+ this.site = this.resolve('site');
+ }
+}
+
+
+
+// ============================================================================
+// Errors
+// ============================================================================
+
+export class ModuleError extends Error { }
+
+export class CyclicDependencyError extends ModuleError {
+ constructor(message, modules) {
+ super(message);
+ this.modules = modules;
+ }
+}
\ No newline at end of file
diff --git a/src/utilities/object.js b/src/utilities/object.js
new file mode 100644
index 00000000..de114c0c
--- /dev/null
+++ b/src/utilities/object.js
@@ -0,0 +1,167 @@
+'use strict';
+
+const HOP = Object.prototype.hasOwnProperty;
+
+export function has(object, key) {
+ return HOP.call(object, key);
+}
+
+
+export function timeout(promise, delay) {
+ return new Promise((resolve, reject) => {
+ let resolved = false;
+ const timer = setTimeout(() => {
+ if ( ! resolved ) {
+ resolved = true;
+ reject(new Error('timeout'));
+ }
+ }, delay);
+
+ promise.then(result => {
+ if ( ! resolved ) {
+ resolved = true;
+ clearTimeout(timer);
+ resolve(result);
+ }
+ }).catch(err => {
+ if ( ! resolved ) {
+ resolved = true;
+ clearTimeout(timer);
+ reject(err);
+ }
+ });
+ });
+}
+
+
+/**
+ * Check that two arrays are the same length and that each array has the same
+ * items in the same indices.
+ * @param {Array} a The first array
+ * @param {Array} b The second array
+ * @returns {boolean} Whether or not they match
+ */
+export function array_equals(a, b) {
+ if ( ! Array.isArray(a) || ! Array.isArray(b) || a.length !== b.length )
+ return false;
+
+ let i = a.length;
+ while(i--)
+ if ( a[i] !== b[i] )
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Special logic to ensure that a target object is matched by a filter.
+ * @param {object} filter The filter object
+ * @param {object} target The object to check it against
+ * @returns {boolean} Whether or not it matches
+ */
+export function filter_match(filter, target) {
+ for(const key in filter) {
+ if ( HOP.call(filter, key) ) {
+ const filter_value = filter[key],
+ target_value = target[key],
+ type = typeof filter_value;
+
+ if ( type === 'function' ) {
+ if ( ! filter_value(target_value) )
+ return false;
+
+ } else if ( Array.isArray(filter_value) ) {
+ if ( Array.isArray(target_value) ) {
+ for(const val of filter_value)
+ if ( ! target_value.includes(val) )
+ return false;
+
+ } else if ( ! filter_value.include(target_value) )
+ return false;
+
+ } else if ( typeof target_value !== type )
+ return false;
+
+ else if ( type === 'object' ) {
+ if ( ! filter_match(filter_value, target_value) )
+ return false;
+
+ } else if ( filter_value !== target_value )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Get a value from an object at a path.
+ * @param {string|Array} path The path to follow, using periods to go down a level.
+ * @param {object|Array} object The starting object.
+ * @returns {*} The value at that point in the path, or undefined if part of the path doesn't exist.
+ */
+export function get(path, object) {
+ if ( typeof path === 'string' )
+ path = path.split('.');
+
+ for(let i=0, l = path.length; i < l; i++) {
+ const part = path[i];
+ if ( part === '@each' ) {
+ const p = path.slice(i + 1);
+ if ( p.length ) {
+ if ( Array.isArray )
+ object = object.map(x => get(p, x));
+ else {
+ const new_object = {};
+ for(const key in object)
+ if ( HOP.call(object, key) )
+ new_object[key] = get(p, object[key]);
+ object = new_object;
+ }
+ }
+
+ break;
+
+ } else
+ object = object[path[i]];
+
+ if ( ! object )
+ break;
+ }
+
+ return object;
+}
+
+
+export function deep_copy(object) {
+ if ( typeof object !== 'object' )
+ return object;
+
+ if ( Array.isArray(object) )
+ return object.map(deep_copy);
+
+ const out = {};
+ for(const key in object)
+ if ( HOP.call(object, key) ) {
+ const val = object[key];
+ if ( typeof val === 'object' )
+ out[key] = deep_copy(val);
+ else
+ out[key] = val;
+ }
+
+ return out;
+}
+
+
+export function maybe_call(fn, ctx, ...args) {
+ if ( typeof fn === 'function' ) {
+ if ( ctx )
+ return fn.call(ctx, ...args);
+ return fn(...args);
+ }
+
+ return fn;
+}
\ No newline at end of file
diff --git a/src/utilities/time.js b/src/utilities/time.js
new file mode 100644
index 00000000..1f58767c
--- /dev/null
+++ b/src/utilities/time.js
@@ -0,0 +1,24 @@
+'use strict';
+
+export function duration_to_string(elapsed, separate_days, days_only, no_hours, no_seconds) {
+ const seconds = elapsed % 60;
+ let minutes = Math.floor(elapsed / 60),
+ hours = Math.floor(minutes / 60),
+ days = '';
+
+ minutes = minutes % 60;
+
+ if ( separate_days ) {
+ days = Math.floor(hours / 24);
+ hours = hours % 24;
+ if ( days_only && days > 0 )
+ return `${days} days`;
+
+ days = days > 0 ? `${days} days, ` : '';
+ }
+
+ return `${days}${
+ (!no_hours || days || hours) ? `${days && hours < 10 ? '0' : ''}${hours}:` : ''
+ }${minutes < 10 ? '0' : ''}${minutes}${
+ no_seconds ? '' : `:${seconds < 10 ? '0' : ''}${seconds}`}`;
+}
\ No newline at end of file
diff --git a/src/utilities/tooltip.js b/src/utilities/tooltip.js
new file mode 100644
index 00000000..1e4ad478
--- /dev/null
+++ b/src/utilities/tooltip.js
@@ -0,0 +1,296 @@
+'use strict';
+
+// ============================================================================
+// Dynamic Tooltip Handling
+//
+// Better because you can assign arbitrary content.
+// Better because they are asynchronous with loading indication.
+// Better because they aren't hidden by parents with overflow: hidden;
+// ============================================================================
+
+import {createElement as e, setChildren} from 'utilities/dom';
+import {maybe_call} from 'utilities/object';
+
+import Popper from 'popper.js';
+
+let last_id = 0;
+
+export const DefaultOptions = {
+ html: false,
+ delayShow: 0,
+ delayHide: 0,
+
+ live: true,
+
+ tooltipClass: 'ffz__tooltip',
+ innerClass: 'ffz__tooltip--inner',
+ arrowClass: 'ffz__tooltip--arrow'
+}
+
+
+// ============================================================================
+// Tooltip Class
+// ============================================================================
+
+export default class Tooltip {
+ constructor(parent, cls, options) {
+ if ( typeof parent === 'string' )
+ parent = document.querySelector(parent);
+
+ if (!( parent instanceof Node ))
+ throw new TypeError('invalid parent');
+
+ this.options = Object.assign({}, DefaultOptions, options);
+ this.live = this.options.live;
+
+ this.parent = parent;
+ this.cls = cls;
+
+ if ( ! this.live ) {
+ if ( typeof cls === 'string' )
+ this.elements = parent.querySelectorAll(cls);
+ else if ( Array.isArray(cls) )
+ this.elements = cls;
+ else if ( cls instanceof Node )
+ this.elements = [cls];
+ else
+ throw new TypeError('invalid elements');
+
+ this.elements = new Set(this.elements);
+
+ } else {
+ this.cls = cls;
+ this.elements = new Set;
+ }
+
+ this._accessor = `_ffz_tooltip$${last_id++}`;
+
+ this._onMouseOut = e => this._exit(e.target);
+
+ if ( this.live ) {
+ this._onMouseOver = e => {
+ const target = e.target;
+ if ( target.classList.contains(this.cls) )
+ this._enter(target);
+ };
+
+ parent.addEventListener('mouseover', this._onMouseOver);
+ parent.addEventListener('mouseout', this._onMouseOut);
+
+ } else {
+ this._onMouseOver = e => {
+ const target = e.target;
+ if ( this.elements.has(target) )
+ this._enter(e.target);
+ }
+
+ if ( this.elements.size <= 5 )
+ for(const el of this.elements) {
+ el.addEventListener('mouseenter', this._onMouseOver);
+ el.addEventListener('mouseleave', this._onMouseOut);
+ }
+
+ else {
+ parent.addEventListener('mouseover', this._onMouseOver);
+ parent.addEventListener('mouseout', this._onMouseOut);
+ }
+
+ }
+ }
+
+ destroy() {
+ if ( this.live || this.elements.size > 5 ) {
+ parent.removeEventListener('mouseover', this._onMouseOver);
+ parent.removeEventListener('mouseout', this._onMouseOut);
+ } else
+ for(const el of this.elements) {
+ el.removeEventListener('mouseenter', this._onMouseOver);
+ el.removeEventListener('mouseleave', this._onMouseOut);
+ }
+
+ for(const el of this.elements) {
+ const tip = el[this._accessor];
+ if ( tip && tip.visible )
+ this.hide(tip);
+
+ el[this._accessor] = null;
+ }
+
+ this.elements = null;
+ this._onMouseOut = this._onMouseOver = null;
+ this.parent = null;
+ }
+
+
+ _enter(target) {
+ let tip = target[this._accessor],
+ delay = this.options.delayShow;
+
+ if ( ! tip )
+ tip = target[this._accessor] = {target};
+
+ tip.state = true;
+
+ if ( tip.visible )
+ return;
+
+ if ( delay === 0 )
+ this.show(tip);
+
+ else {
+ if ( tip._show_timer )
+ clearTimeout(tip._show_timer);
+
+ tip._show_timer = setTimeout(() => {
+ tip._show_timer = null;
+ if ( tip.state )
+ this.show(tip);
+ }, delay);
+ }
+ }
+
+ _exit(target) {
+ const tip = target[this._accessor];
+ if ( ! tip || ! tip.visible )
+ return;
+
+ const delay = this.options.delayHide;
+
+ tip.state = false;
+
+ if ( delay === 0 )
+ this.hide(tip);
+
+ else {
+ if ( tip._show_timer )
+ clearTimeout(tip._show_timer);
+
+ tip._show_timer = setTimeout(() => {
+ tip._show_timer = null;
+ if ( ! tip.state )
+ this.hide(tip);
+
+ }, delay);
+ }
+ }
+
+
+ show(tip) {
+ const opts = this.options,
+ target = tip.target;
+
+ this.elements.add(target);
+
+ // Set this early in case content uses it early.
+ tip.update = () => tip._update(); // tip.popper && tip.popper.scheduleUpdate();
+ tip.rerender = () => {
+ if ( tip.visible ) {
+ this.hide(tip);
+ this.show(tip);
+ }
+ }
+
+ let content = maybe_call(opts.content, null, target, tip);
+ if ( content === undefined )
+ content = tip.target.title;
+
+ if ( tip.visible || (! content && ! opts.onShow) )
+ return;
+
+ // Build the DOM.
+ const arrow = e('div', opts.arrowClass),
+ inner = tip.element = e('div', opts.innerClass),
+
+ el = tip.outer = e('div', {
+ className: opts.tooltipClass
+ }, [inner, arrow]);
+
+ arrow.setAttribute('x-arrow', true);
+
+ if ( maybe_call(opts.interactive, null, target, tip) ) {
+ el.classList.add('interactive');
+ el.addEventListener('mouseover', () => this._enter(target));
+ el.addEventListener('mouseout', () => this._exit(target));
+ }
+
+ // Assign our content. If there's a Promise, we'll need
+ // to do this weirdly.
+ const use_html = maybe_call(opts.html, null, target, tip),
+ setter = use_html ? 'innerHTML' : 'textContent';
+
+ const pop_opts = Object.assign({
+ arrowElement: arrow,
+ }, opts.popper);
+
+ tip._update = () => {
+ if ( tip.popper ) {
+ tip.popper.destroy();
+ tip.popper = new Popper(target, el, pop_opts);
+ }
+ }
+
+ if ( content instanceof Promise ) {
+ inner.innerHTML = '
';
+ content.then(content => {
+ if ( ! content )
+ return this.hide(tip);
+
+ if ( use_html && (content instanceof Node || Array.isArray(content)) ) {
+ inner.innerHTML = '';
+ setChildren(inner, content, opts.sanitizeChildren);
+ } else
+ inner[setter] = content;
+
+ tip._update();
+
+ }).catch(err => {
+ inner.textContent = `There was an error showing this tooltip.\n${err}`;
+ tip._update();
+ });
+
+ } else if ( content ) {
+ if ( use_html && (content instanceof Node || Array.isArray(content)) )
+ setChildren(inner, content, opts.sanitizeChildren);
+ else
+ inner[setter] = content;
+ }
+
+
+ // Add everything to the DOM and create the Popper instance.
+ this.parent.appendChild(el);
+ tip.popper = new Popper(target, el, pop_opts);
+ tip.visible = true;
+
+ if ( opts.onShow )
+ opts.onShow(target, tip);
+ }
+
+
+ hide(tip) { // eslint-disable-line class-methods-use-this
+ const opts = this.options;
+ if ( opts.onHide )
+ opts.onHide(tip.target, tip);
+
+ if ( tip.popper ) {
+ tip.popper.destroy();
+ tip.popper = null;
+ }
+
+ if ( tip.outer ) {
+ const o = tip.outer;
+ if ( o.parentElement )
+ o.parentElement.removeChild(o);
+
+ tip.outer = null;
+ }
+
+ tip.update = null;
+ tip._update = noop;
+ tip.element = null;
+ tip.visible = false;
+ }
+}
+
+
+// Function Intentionally Left Blank
+function noop() { }
\ No newline at end of file
diff --git a/src/utilities/vue.js b/src/utilities/vue.js
new file mode 100644
index 00000000..d30295e8
--- /dev/null
+++ b/src/utilities/vue.js
@@ -0,0 +1,99 @@
+'use strict';
+
+// ============================================================================
+// Vue Library
+// Loads Vue + Translation Shim
+// ============================================================================
+
+import Module from 'utilities/module';
+import {has} from 'utilities/object';
+
+
+export default class Vue extends Module {
+ constructor(...args) {
+ super(...args);
+ this._components = {};
+ this.inject('i18n');
+ }
+
+ async onLoad() {
+ const Vue = this.Vue = (await import(/* webpackChunkName: "vue" */ 'vue')).default,
+ components = this._components;
+
+ this.component((await import(/* webpackChunkName: "vue" */ 'src/std-components/index.js')).default);
+
+ for(const key in components)
+ if ( has(components, key) )
+ Vue.component(key, components[key]);
+
+ this._components = null;
+ Vue.use(this);
+ }
+
+ component(name, component) {
+ if ( typeof name === 'function' ) {
+ for(const key of name.keys())
+ this.component(key.slice(2, key.length - 4), name(key).default);
+
+ } else if ( typeof name === 'object' ) {
+ for(const key in name)
+ if ( has(name, key) )
+ this.component(key, name[key]);
+
+ } else if ( this.Vue )
+ this.Vue.component(name, component);
+
+ else
+ this._components[name] = component;
+ }
+
+ install(vue) {
+ // This is a mess. I'm sure there's an easier way to tie the systems
+ // together. However, for now, this works.
+
+ const t = this;
+ if ( ! this._vue_i18n ) {
+ this._vue_i18n = new this.Vue({
+ data() {
+ return {
+ locale: t.i18n.locale,
+ phrases: {}
+ }
+ },
+
+ methods: {
+ t_(key, phrase, options) {
+ this.locale && this.phrases[key];
+ return t.i18n.t(key, phrase, options);
+ },
+
+ setLocale(locale) {
+ t.i18n.locale = locale;
+ }
+ }
+ });
+
+ this.on('i18n:changed', () => {
+ this._vue_i18n.locale = this.i18n.locale;
+ this._vue_i18n.phrases = {};
+ });
+
+ this.on('i18n:loaded', keys => {
+ const i = this._vue_i18n,
+ p = i.phrases;
+ for(const key of keys)
+ i.$set(p, key, (p[key]||0) + 1);
+ });
+
+ vue.prototype.$i18n = this._vue_i18n;
+ }
+
+ vue.mixin({
+ methods: {
+ t(key, phrase, options) {
+ return this.$i18n.t_(key, phrase, options);
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/utils.js b/src/utils.js
deleted file mode 100644
index 3f8b8b39..00000000
--- a/src/utils.js
+++ /dev/null
@@ -1,1408 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- constants = require('./constants'),
- WEBKIT = constants.IS_WEBKIT ? '-webkit-' : '';
-
-
-var createElement = function(tag, className, content) {
- var out = document.createElement(tag);
- if ( className )
- out.className = className;
- if ( content )
- if ( content.nodeType )
- out.appendChild(content);
- else
- out.innerHTML = content;
-
- return out;
- },
-
- sanitize_el = createElement('span'),
-
- sanitize = function(msg) {
- sanitize_el.textContent = msg;
- return sanitize_el.innerHTML;
- },
-
- unquote_attr = function(msg) {
- return msg.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, "'").replace(/>/g, '>').replace(/</g, '<');
- },
-
- escape_regex = RegExp.escape || function(str) {
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- },
-
- R_QUOTE = /"/g,
- R_SQUOTE = /'/g,
- R_AMP = /&/g,
- R_LT = //g,
-
- DURATIONS = {},
-
- quote_attr = function(msg) {
- return msg.replace(R_AMP, "&").replace(R_QUOTE, """).replace(R_SQUOTE, "'").replace(R_LT, "<").replace(R_GT, ">");
- },
-
- quote_san = function(msg) {
- return sanitize(msg).replace(R_QUOTE, """).replace(R_SQUOTE, "'");
- },
-
- HUMAN_NUMBERS = [
- "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
- ],
-
- number_commas = function(x) {
- var parts = x.toString().split(".");
- parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
- return parts.join(".");
- },
-
- human_time = function(elapsed, factor) {
- factor = factor || 1;
- elapsed = Math.floor(elapsed);
-
- var years = Math.floor((elapsed*factor) / 31536000) / factor;
- if ( years >= 1 )
- return years + ' year' + pluralize(years);
-
- var days = Math.floor((elapsed %= 31536000) / 86400);
- if ( days >= 1 )
- return days + ' day' + pluralize(days);
-
- var hours = Math.floor((elapsed %= 86400) / 3600);
- if ( hours >= 1 )
- return hours + ' hour' + pluralize(hours);
-
- var minutes = Math.floor((elapsed %= 3600) / 60);
- if ( minutes >= 1 )
- return minutes + ' minute' + pluralize(minutes);
-
- var seconds = elapsed % 60;
- if ( seconds >= 1 )
- return seconds + ' second' + pluralize(seconds);
-
- return 'less than a second';
- },
-
- pluralize = function(value, singular, plural) {
- plural = plural || 's';
- singular = singular || '';
- return value === 1 ? singular : plural;
- },
-
- place_string = function(num) {
- if ( num == 1 ) return '1st';
- else if ( num == 2 ) return '2nd';
- else if ( num == 3 ) return '3rd';
- else if ( num == null ) return '---';
- return num + "th";
- },
-
- lv_duration_regex = / ?(\d+) ?(\w+)/g,
-
- date_regex = /^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/,
-
- parse_date = function(str) {
- if ( str === null )
- return null;
- else if ( typeof str === "number" )
- return new Date(str);
-
- var parts = str.match(date_regex);
- if ( ! parts )
- return null;
-
- parts[7] = (parts[7] && parts[7].length) ? parts[7].substr(0, 3) : 0;
-
- var unix = Date.UTC(parts[1], parts[2] - 1, parts[3], parts[4], parts[5], parts[6], parts[7]);
-
- // Check Offset
- if ( parts[9] ) {
- var offset = (parts[9] == "-" ? 1 : -1) * 60000 * (60*parts[10] + 1*parts[11]);
- unix += offset;
- }
-
- return new Date(unix);
- },
-
-
- /* IRC Processing */
-
- irc_regex = /^(?:@([^ ]+) )?(?:[:](\S+) )?(\S+)(?: (?!:)(.+?))?(?: [:](.+))?$/,
- tag_regex = /([^=;]+)=([^;]*)/g,
-
- parse_badge_tag = function(tag) {
- var badges = {},
- values = tag.split(',');
-
- for(var i=0; i < values.length; i++) {
- var parts = values[i].split('/');
- if ( parts.length === 2 )
- badges[parts[0]] = parts[1];
- }
-
- return badges;
- },
-
- parse_emote_tag = function(tag) {
- var emotes = {},
- values = tag.split("/"),
- i = values.length;
-
- while(i--) {
- var parts = values[i].split(":");
- if ( parts.length !== 2 )
- return {};
-
- var emote_id = parts[0],
- matches = emotes[emote_id] = [],
- indices = parts[1].split(",");
-
- for(var j=0, jl = indices.length; j < jl; j++) {
- var pair = indices[j].split("-");
- if ( pair.length !== 2 )
- return {};
-
- var start = parseInt(pair[0]),
- end = parseInt(pair[1]);
-
- matches.push([start,end]);
- }
- }
-
- return emotes;
- },
-
- parse_tag = function(tag, value) {
- switch (tag) {
- case "badges":
- return parse_badge_tag(value);
- case "emotes":
- return parse_emote_tag(value);
- case "sent-ts":
- case "sent-tmi-ts":
- case "slow":
- return +value;
- case "subscriber":
- case "mod":
- case "turbo":
- case "r9k":
- case "subs-only":
- case "historical":
- return value === "1";
- default:
- // Try to unescape the value.
- try {
- return unescape_tag_value(value);
- } catch(err) {
- return "";
- }
- }
- },
-
- parse_tags = function(raw_tags) {
- var m, tags = {};
- do {
- m = tag_regex.exec(raw_tags);
- if ( m )
- tags[m[1]] = parse_tag(m[1], m[2]);
-
- } while(m);
-
- return tags;
- },
-
- ESCAPE_CHARS = {
- ';': '\\:',
- ' ': '\\s',
- '\r': '\\r',
- '\n': '\\n',
- '\\': '\\\\'
- },
-
- UNESCAPE_CHARS = {
- ':': ';',
- 's': ' ',
- 'r': '\r',
- 'n': '\n'
- },
-
- unescape_tag_value = function(value) {
- var result = '';
- for(var i=0,l=value.length; i < l; i++) {
- var c = value.charAt(i);
- if ( c === '\\' ) {
- if ( i === l - 1 )
- throw "Improperly escaped tag";
-
- i++;
- c = value.charAt(i);
- result += UNESCAPE_CHARS[c] || c;
-
- } else
- result += c;
- }
- return result;
- },
-
- escape_tag_value = function(value) {
- var value_str = ''+value,
- result = '';
-
- for(var i=0,l=value_str.length; i < l; i++) {
- var c = value_str.charAt(i);
- result += ESCAPE_CHARS[c] || c;
- }
-
- return result;
- },
-
- format_tag = function(tag, value) {
- switch(tag) {
- case "subscriber":
- case "mod":
- case "turbo":
- case "r9k":
- case "subs-only":
- case "historical":
- return value ? '1' : '0';
- default:
- return escape_tag_value(value)
- }
- },
-
- build_tags = function(tags) {
- var raw_tags = [];
- for(var key in tags) {
- if ( ! tags.hasOwnProperty(key) )
- continue;
- raw_tags.push(key + '=' + format_tag(key, tags[key]));
- }
- return raw_tags.join(';');
- },
-
- parse_sender = function(prefix, tags) {
- var ind = prefix.indexOf('!');
- if ( ind !== -1 )
- return prefix.substr(0, ind);
- if ( prefix === "tmi.twitch.tv" && tags.login )
- return tags.login;
- return prefix;
- },
-
- parse_irc_message = function(message) {
- var data = irc_regex.exec(message);
- if ( ! data )
- return null;
-
- var m,
- tags = {},
- output = {
- tags: tags,
- prefix: data[2],
- command: data[3],
- params: data[4],
- trailing: data[5]
- };
-
- if ( data[1] )
- output.tags = parse_tags(data[1]);
-
- return output;
- },
-
-
- parse_irc_privmsg = function(message) {
- var parsed = parse_irc_message(message);
- if ( parsed.command.toLowerCase() !== "privmsg" )
- return null;
-
- var params = (parsed.params || "").split(' '),
- target = params.shift();
-
- if ( target.charAt(0) !== '#' )
- return null;
-
- if ( parsed.trailing )
- params.push(parsed.trailing);
-
- var from = parse_sender(parsed.prefix),
- message = params.join(' '),
- style = '';
-
- if ( from === 'jtv' )
- style = 'admin';
- else if ( from === 'twitchnotify' )
- style = 'notification';
-
- if ( message.substr(0,8) === '\u0001ACTION ' && message.charAt(message.length-1) === '\u0001' ) {
- message = message.substr(8, message.length - 9);
- style += (style ? ' ' : '') + 'action';
- }
-
- return {
- tags: parsed.tags,
- from: parse_sender(parsed.prefix),
- room: target.substr(1),
- message: message,
- style: style
- }
- },
-
-
- BADGE_REV = {
- 'b': 'broadcaster',
- 's': 'staff',
- 'a': 'admin',
- 'g': 'global_mod',
- 'm': 'moderator',
- 'u': 'subscriber',
- 't': 'turbo'
- },
-
- uncompressBadges = function(value) {
- if ( value === true )
- return {};
-
- var output = {},
- badges = value.split(","),
- l = badges.length;
-
- for(var i=0; i < l; i++) {
- var parts = badges[i].split('/');
- if ( parts.length !== 2 )
- return {};
-
- output[BADGE_REV[parts[0]] || parts[0].substr(1)] = parts[1];
- }
-
- return output;
- },
-
- uncompressEmotes = function(value) {
- var output = {},
- emotes = value.split("/"),
- i = emotes.length;
-
- while(i--) {
- var parts = emotes[i].split(":");
- if ( parts.length !== 3 )
- return {};
-
- var emote_id = parts[0],
- length = parseInt(parts[1]),
- positions = parts[2].split(","),
- indices = output[emote_id] = output[emote_id] || [];
-
- for(var j=0, jl = positions.length; j < jl; j++) {
- var start = parseInt(positions[j]),
- end = start + length;
-
- for(var x=0, xl = indices.length; x < xl; x++) {
- if ( start < indices[x][0] )
- break;
- }
-
- indices.splice(x, 0, [start, end]);
- }
- }
-
- return output;
- },
-
-
- // This code borrowed from the twemoji project, with tweaks.
- UFE0Fg = /\uFE0F/g,
- U200D = String.fromCharCode(0x200D),
-
- EMOJI_CODEPOINTS = {},
- emoji_to_codepoint = function(surrogates) {
- if ( EMOJI_CODEPOINTS[surrogates] )
- return EMOJI_CODEPOINTS[surrogates];
-
- var input = surrogates.indexOf(U200D) === -1 ? surrogates.replace(UFE0Fg, '') : surrogates,
- out = [],
- c = 0, p = 0, i = 0;
-
- while (i < input.length) {
- c = input.charCodeAt(i++);
- if ( p ) {
- out.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
- p = 0;
- } else if ( 0xD800 <= c && c <= 0xDBFF )
- p = c;
- else
- out.push(c.toString(16));
- }
-
- var retval = EMOJI_CODEPOINTS[surrogates] = out.join('-');
- return retval;
- },
-
- codepoint_to_emoji = function(codepoint) {
- var code = typeof codepoint === 'string' ? parseInt(codepoint, 16) : codepoint;
- if ( code < 0x10000 )
- return String.fromCharCode(code);
- code -= 0x10000;
- return String.fromCharCode(
- 0xD800 + (code >> 10),
- 0xDC00 + (code & 0x3FF)
- )
- },
-
-
- // Twitch Emote Helpers
-
- SRCSETS = {},
- build_srcset = function(id) {
- if ( SRCSETS[id] )
- return SRCSETS[id];
- var out = SRCSETS[id] = constants.TWITCH_BASE + id + "/1.0 1x, " + constants.TWITCH_BASE + id + "/2.0 2x";
- return out;
- },
-
-
- // Twitch API
-
- api_call = function(method, url, data, options, token) {
- options = options || {};
- var headers = options.headers = options.headers || {};
- headers['Client-ID'] = constants.CLIENT_ID;
- if ( token )
- headers.Authorization = 'OAuth ' + token;
- return Twitch.api[method].call(this, url, data, options);
- },
-
-
- logviewer_call = function(method, url, token, info) {
- info = info || {};
- info['method'] = method;
- if ( token )
- url += (url.indexOf('?') === -1 ? '?' : '&') + 'token=' + token;
-
- return fetch("https://cbenni.com/api/" + url, info);
- },
-
-
- // Dialogs
- show_modal = function(contents, on_close, width) {
- var container = createElement('div', 'twitch_subwindow_container'),
- subwindow = createElement('div', 'twitch_subwindow ffz-subwindow'),
- card = createElement('div', 'card'),
- close_button = createElement('div', 'modal-close-button', constants.CLOSE),
-
- closer = function() { container.parentElement.removeChild(container) };
-
- container.id = 'ffz-modal-container';
-
- subwindow.style.width = '100%';
- subwindow.style.maxWidth = (width||420) + 'px';
-
- close_button.addEventListener('click', function() {
- closer();
- if ( on_close )
- on_close(false);
- });
-
- container.appendChild(subwindow);
- subwindow.appendChild(card);
- subwindow.appendChild(close_button);
-
- card.appendChild(contents);
-
- var el = document.querySelector('app-main');
-
- if ( el )
- el.parentElement.insertBefore(container, el.nextSibling);
- else
- document.body.appendChild(container);
-
- return closer;
- },
-
-
- ember_lookup = function(thing) {
- if ( ! window.App )
- return;
-
- try {
- if ( App.__deprecatedInstance__ && App.__deprecatedInstance__.registry && App.__deprecatedInstance__.registry.lookup )
- return App.__deprecatedInstance__.registry.lookup(thing);
- if ( App.__container__ && App.__container__.lookup )
- return App.__container__.lookup(thing);
- } catch(err) {
- FrankerFaceZ.get().error("There was an error looking up an Ember instance: " + thing, err);
- return null;
- }
- },
-
- ember_resolve = function(thing) {
- if ( ! window.App )
- return;
-
- if ( App.__deprecatedInstance__ && App.__deprecatedInstance__.registry && App.__deprecatedInstance__.registry.resolve )
- return App.__deprecatedInstance__.registry.resolve(thing);
- if ( App.__container__ && App.__container__.resolve )
- return App.__container__.resolve(thing);
- },
-
-
- ember_transition = function(route, model) {
- var router = ember_lookup('router:main');
- if ( model )
- router.transitionTo(route, model);
- else
- router.transitionTo(route);
- },
-
-
- CMD_VAR_REGEX = /{(\d+(?:\$(?:\d+)?)?|id|msg_id|message_id|(?:user|room)(?:_id|_name|_display_name)?)}/g;
-
-
-module.exports = FFZ.utils = {
- // Ember Manipulation
- ember_views: function() {
- return ember_lookup('-view-registry:main') || {};
- },
-
- ember_lookup: ember_lookup,
- ember_resolve: ember_resolve,
- ember_settings: function() {
- var settings = ember_resolve('model:settings');
- return settings && settings.findOne();
- },
-
- transition: ember_transition,
- transition_game: function(game) {
- if ( game === "Counter-Strike: Global Offensive" )
- ember_transition('directory.csgo.channels.index')
- else if ( game === "Creative" )
- ember_transition('directory.creative.index')
- else
- ember_transition('directory.game.index', game)
- },
-
- transition_user: function(username) {
- var Channel = ember_resolve('model:deprecated-channel');
- ember_transition('channel.index', Channel.find({id: username}).load());
- return false;
- },
-
- transition_link: function(callback) {
- return function(e) {
- if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
- return;
-
- e.preventDefault();
- jQuery('.tipsy').remove();
-
- callback.call(this, e);
- return false;
- }
- },
-
- ember_reopen_view: function(component, data) {
- if ( typeof component === 'string' )
- component = ember_resolve(component);
-
- data.ffz_modified = true;
-
- if ( data.ffz_init && ! data.didInsertElement )
- data.didInsertElement = function() {
- this._super();
- try {
- this.ffz_init();
- } catch(err) {
- FFZ.get().error("An error occured running ffz_init on " + this.toString(), err);
- }
- };
-
- if ( data.ffz_destroy && ! data.willClearRender )
- data.willClearRender = function() {
- try {
- this.ffz_destroy();
- } catch(err) {
- FFZ.get().error("An error occured running ffz_destroy on " + this.toString(), err);
- }
-
- this._super();
- };
-
- return component.reopen(data);
- },
-
- // Other Stuff
-
- process_int: function(default_value, false_value, true_value) {
- return function(val) {
- if ( val === false && false_value !== undefined )
- val = false_value;
- else if ( val === true && true_value !== undefined )
- val = true_value;
- else if ( typeof val === "string" ) {
- val = parseInt(val);
- if ( isNaN(val) || ! isFinite(val) )
- val = default_value;
- }
- return val;
- }
- },
-
- build_srcset: build_srcset,
- /*build_tooltip: build_tooltip,
- load_emote_data: load_emote_data,*/
-
- api: {
- del: function(u,d,o,t) { return api_call('del', u,d,o,t); },
- get: function(u,d,o,t) { return api_call('get', u,d,o,t); },
- post: function(u,d,o,t) { return api_call('post', u,d,o,t); },
- put: function(u,d,o,t) { return api_call('put', u,d,o,t); }
- },
-
- logviewer: {
- del: function(u,t,i) { return logviewer_call('delete', u,t,i) },
- get: function(u,t,i) { return logviewer_call('get', u,t,i) },
- post: function(u,t,i) { return logviewer_call('post', u,t,i) },
- put: function(u,t,i) { return logviewer_call('put', u,t,i) },
- },
-
- json: function(response) {
- if ( ! response.ok )
- return Promise.resolve(null);
- return response.json();
- },
-
-
- find_parent: function(el, klass) {
- while (el && el.parentNode) {
- el = el.parentNode;
- if ( el.classList.contains(klass) )
- return el;
- }
-
- return null;
- },
-
-
- parse_badge_tag: parse_badge_tag,
- parse_emote_tag: parse_emote_tag,
- parse_tags: parse_tags,
- parse_irc_message: parse_irc_message,
- parse_irc_privmsg: parse_irc_privmsg,
-
- format_tag: format_tag,
- escape_tag_value: escape_tag_value,
- unescape_tag_value: unescape_tag_value,
- build_tags: build_tags,
-
- CMD_VAR_REGEX: CMD_VAR_REGEX,
-
- extract_cmd_variables: function(command, args_only) {
- var matches = [];
-
- CMD_VAR_REGEX.lastIndex = 0;
- command.replace(CMD_VAR_REGEX, function(match, variable) {
- if ( args_only && ! /\d+(?:$\d*)?/.test(variable) )
- return;
- matches.push('{' + variable + '}');
- });
-
- return _.unique(matches);
- },
-
- replace_cmd_variables: function(command, user, room, message, args) {
- user = user || {};
- room = room || {};
- message = message || {};
- message.tags = message.tags || {};
-
- var msg_id = message.tags.id,
- replacements = {
- user: user.name,
- user_name: user.name,
- user_display_name: user.display_name || message.tags['display-name'],
- user_id: user._id || message.tags['user-id'],
-
- room: room.id,
- room_name: room.id,
- room_display_name: room.get && (room.get('tmiRoom.displayName') || room.get('channel.displayName')),
- room_id: room.get && room.get('roomProperties._id') || message.tags['room-id'],
-
- id: msg_id,
- message_id: msg_id,
- msg_id: msg_id
- };
-
- CMD_VAR_REGEX.lastIndex = 0;
- return command.replace(CMD_VAR_REGEX, function(match, variable) {
- if ( replacements[variable] )
- return replacements[variable];
-
- if ( args ) {
- var match = /(\d+)(?:(\$)(\d+)?)?/.exec(variable);
- if ( match ) {
- var num = parseInt(match[1]),
- second_num = match[3] ? parseInt(match[3]) : undefined;
-
- return match[2] === '$' ? args.slice(num, second_num).join(" ") : args[num];
- }
- }
-
- return '{' + variable + '}';
- });
- },
-
-
- show_modal: show_modal,
- confirm: function(title, description, callback) {
- var contents = createElement('div', 'text-content'),
- heading = title ? createElement('div', 'content-header', '' + title + ' ') : null,
- body = createElement('div', 'item'),
- buttons = createElement('div', 'buttons', 'Cancel OK '),
-
- close_btn = buttons.querySelector('.js-subwindow-close'),
- okay_btn = buttons.querySelector('.button.primary');
-
- if ( heading )
- contents.appendChild(heading);
-
- if ( description ) {
- if ( description.nodeType )
- body.appendChild(description);
- else
- body.innerHTML = '' + description + '
';
-
- contents.appendChild(body);
- }
-
- contents.appendChild(buttons);
-
- var closer,
- cb = function(success) {
- closer();
- if ( ! callback )
- return;
-
- callback(success);
- };
-
- closer = show_modal(contents, cb);
-
- okay_btn.addEventListener('click', function(e) { e.preventDefault(); cb(true); return false });
- close_btn.addEventListener('click', function(e) { e.preventDefault(); cb(false); return false });
- },
-
- prompt: function(title, description, old_value, callback, width, input) {
- var contents = createElement('div', 'text-content'),
- heading = createElement('div', 'content-header', '' + title + ' '),
- form = createElement('form'),
- close_btn, okay_btn;
-
- if ( ! input ) {
- input = createElement('input');
- input.type = 'text';
- }
-
- form.innerHTML = '' + (description ? '
' + description + '
' : '') + '
';
-
- var ph = form.querySelector('.input-placeholder'),
- par = ph.parentElement;
-
- par.insertBefore(input, ph);
- par.removeChild(ph);
-
- contents.appendChild(heading);
- contents.appendChild(form);
-
- close_btn = form.querySelector('.js-subwindow-close');
- okay_btn = form.querySelector('.button.primary');
-
- if ( old_value !== undefined )
- input.value = old_value;
-
- var closer,
- cb = function(success) {
- closer();
- if ( ! callback )
- return;
-
- callback(success ? input.value : null);
- };
-
- closer = show_modal(contents, cb, width);
-
- try {
- input.focus();
- } catch(err) { }
-
- form.addEventListener('submit', function(e) { e.preventDefault(); cb(true); return false });
- okay_btn.addEventListener('click', function(e) { e.preventDefault(); cb(true); return false });
- close_btn.addEventListener('click', function(e) { e.preventDefault(); cb(false); return false });
- },
-
-
- last_minute: function() {
- var now = new Date();
- if ( now.getSeconds() >= 30 )
- now.setMinutes(now.getMinutes()+1);
-
- now.setSeconds(0);
- now.setMilliseconds(0);
- return now.getTime();
- },
-
- maybe_chart: function(series, point, render, force) {
- var len = series.data.length;
- if ( force || point.y !== null || (len > 0 && series.data[len-1].y !== null) ) {
- series.addPoint(point, render);
- return true;
- }
- return false;
- },
-
- update_css: function(element, id, css) {
- var all = element.innerHTML,
- start = "/*BEGIN " + id + "*/",
- end = "/*END " + id + "*/",
- s_ind = all.indexOf(start),
- e_ind = all.indexOf(end),
- found = s_ind !== -1 && e_ind !== -1 && e_ind > s_ind;
-
- if ( !found && !css )
- return;
-
- if ( found )
- all = all.substr(0, s_ind) + all.substr(e_ind + end.length);
-
- if ( css )
- all += start + css + end;
-
- element.innerHTML = all;
- },
-
-
- tooltip_placement: function(margin, prefer) {
- return function(box) {
- var pref = prefer;
- if ( typeof pref === "function" )
- pref = pref.call(this);
-
- var dir = {};
-
- if ( pref.indexOf('n') !== -1 )
- dir.ns = 'n';
- else if ( pref.indexOf('s') !== -1 )
- dir.ns = 's';
-
- if ( pref.indexOf('e') !== -1 )
- dir.ew = 'e';
- else if ( pref.indexOf('w') !== -1 )
- dir.ew = 'w';
-
- var $this = $(this),
- half_width = $this.width() / 2,
- half_height = $this.height() / 2,
- boundTop = $(document).scrollTop() + half_height + (margin*2),
- boundLeft = $(document).scrollLeft() + half_width + margin;
-
- if ($this.offset().top < boundTop) dir.ns = 'n';
- if ($this.offset().left < boundLeft) dir.ew = 'w';
- if ($(window).width() + $(document).scrollLeft() - ($this.offset().left + half_width) < margin) dir.ew = 'e';
- if ($(window).height() + $(document).scrollTop() - ($this.offset().top + half_height) < (2*margin)) dir.ns = 's';
-
- return (dir.ns ? dir.ns : '') + (dir.ew ? dir.ew : '');
- }
- },
-
- newtip_placement: function(margin, prefer) {
- return function(box) {
- var pref = prefer;
- if ( typeof pref === 'function' )
- pref = pref.call(this);
-
- var dir = {},
-
- win = jQuery(window),
- t = jQuery(this),
-
- offset = t.offset(),
-
- width = box.width,
- height = box.height,
-
- d_st = document.body.scrollTop,
- d_sl = document.body.scrollLeft;
-
- if ( pref.length > 1 ) {
- dir.ns = pref[0];
- dir.ew = pref[1];
- } else if ( pref[0] === 'e' || pref[0] === 'w' )
- dir.ew = pref[0];
- else
- dir.ns = pref[0];
-
- if ( offset.top < d_st + margin ) dir.ns = 'n';
- if ( offset.left < d_sl + margin ) dir.ew = 'w';
- if ( win.width() + d_sl - (offset.left + width) < margin ) dir.ew = 'e';
- if ( win.height() + d_st - (offset.top + height) < margin ) dir.ns = 's';
-
- return (dir.ns ? dir.ns : '') + (dir.ew ? dir.ew : '');
- }
- },
-
- uncompressBadges: uncompressBadges,
- uncompressEmotes: uncompressEmotes,
-
- emoji_to_codepoint: emoji_to_codepoint,
- codepoint_to_emoji: codepoint_to_emoji,
-
- parse_date: parse_date,
-
- number_commas: number_commas,
-
- place_string: place_string,
-
- placement: function(entrant) {
- if ( entrant.state == "forfeit" ) return "Forfeit";
- else if ( entrant.state == "dq" ) return "DQed";
- else if ( entrant.place ) return place_string(entrant.place);
- return "";
- },
-
- sanitize: sanitize,
- unquote_attr: unquote_attr,
- quote_attr: quote_attr,
- quote_san: quote_san,
-
- date_string: function(date) {
- return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate();
- },
-
- pluralize: pluralize,
-
- human_number: function(value) {
- return HUMAN_NUMBERS[value] || number_commas(value);
- },
-
- human_time: human_time,
- full_human_time: function(elapsed, factor) {
- var before = elapsed >= 0,
- output = human_time(Math.abs(elapsed), factor);
-
- return before ? output + ' ago' : 'in ' + output;
- },
-
- human_join: function(list) {
- if ( list.length === 2 )
- return list[0] + ' and ' + list[1];
- else if ( list.length === 1 )
- return list[0];
- return list.slice(0, -1).join(', ') + ' and ' + list[list.length-1];
- },
-
- time_to_string: function(elapsed, separate_days, days_only, no_hours, no_seconds) {
- var seconds = elapsed % 60,
- minutes = Math.floor(elapsed / 60),
- hours = Math.floor(minutes / 60),
- days = null;
-
- minutes = minutes % 60;
-
- if ( separate_days ) {
- days = Math.floor(hours / 24);
- hours = hours % 24;
- if ( days_only && days > 0 )
- return days + " days";
-
- days = ( days > 0 ) ? days + " days, " : "";
- }
-
- return (days||'') + ((!no_hours || days || hours) ? ((days && hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + (no_seconds ? "" : (":" + (seconds < 10 ? "0" : "") + seconds));
- },
-
- duration_string: function(val, no_purge, full_names) {
- if ( ! no_purge && val === 1 )
- return 'Purge';
-
- if ( ! full_names && DURATIONS[val] )
- return DURATIONS[val];
-
- var weeks, days, hours, minutes, seconds;
-
- weeks = Math.floor(val / 604800);
- seconds = val % 604800;
-
- days = Math.floor(seconds / 86400);
- seconds %= 86400;
-
- hours = Math.floor(seconds / 3600);
- seconds %= 3600;
-
- minutes = Math.floor(seconds / 60);
- seconds %= 60;
-
- var out = (weeks ? weeks + (full_names ? ' week' + pluralize(weeks) + ' ' : 'w') : '') +
- (days ? days + (full_names ? ' day' + pluralize(days) + ' ' : 'd') : '') +
- (hours ? hours + (full_names ? ' hour' + pluralize(hours) + ' ' : 'h') : '') +
- (minutes ? minutes + (full_names ? ' minute' + pluralize(minutes) + ' ' : 'm') : '') +
- (seconds ? seconds + (full_names ? ' second' + pluralize(seconds) + ' ' : 's') : '');
-
- if ( full_names )
- return out.substr(0, out.length - 1);
-
- DURATIONS[val] = out;
- return out;
- },
-
- parse_lv_duration: function(input) {
- var match, value = 0;
- while(match = lv_duration_regex.exec(input)) {
- var mod = match[2],
- val = parseInt(match[1]);
- if ( mod === 'd' )
- value += val * 86400;
- else if ( mod === 'hrs' )
- value += val * 3600;
- else if ( mod === 'min' )
- value += val * 60;
- else if ( mod === 'sec' )
- value += val;
- }
-
- return value;
- },
-
- format_unread: function(count) {
- if ( count < 1 )
- return "";
-
- else if ( count >= 99 )
- return "99+";
-
- return "" + count;
- },
-
- format_size: function(bits) {
- if(Math.abs(bits) < 1024)
- return bits + ' b';
-
- var units = ['Kb','Mb','Gb','Tb','Pb','Eb','Zb','Yb'],
- u = -1;
- do {
- bits /= 1024;
- ++u;
- } while(Math.abs(bits) >= 1024 && u < units.length - 1);
- return bits.toFixed(1) + ' ' + units[u];
- },
-
- escape_regex: escape_regex,
-
- createElement: createElement,
-
- toggle_cls: function(cls) {
- return function(val) {
- document.body.classList.toggle(cls, val);
- }
- },
-
-
- find_common_prefix: function(list, lower_only) {
- if ( ! list || ! list.length )
- return 0;
-
- var p = 0,
- l = list.length,
- w = lower_only ? list[0].toLowerCase() : list[0],
- wl = w.length;
-
- while (p < wl) {
- var c = w[p];
- for(var i=0; i < l; ++i)
- if ( list[i][p] !== c)
- return p;
-
- ++p;
- }
-
- return wl;
- },
-
-
- utf8_encode: function(text) {
- return unescape(encodeURIComponent(text))
- },
-
- utf8_decode: function(text) {
- return decodeURIComponent(escape(text));
- },
-
- emote_css: function(emote) {
- var output = '';
- if ( ! emote.margins && (!emote.modifier || (! emote.modifier_offset && ! emote.extra_width && ! emote.shrink_to_fit)) && ! emote.css )
- return output;
-
- if ( emote.modifier && (emote.modifier_offset || emote.margins || emote.extra_width || emote.shrink_to_fit) ) {
- var margins = emote.modifier_offset || emote.margins || '0';
- margins = _.map(margins.split(/\s+/), function(n) { return parseInt(n) });
- if ( margins.length === 3 )
- margins.push(margins[1]);
-
- var l = margins.length,
- m_left = margins[3 % l],
- m_right = margins[1 % l],
- m_top = margins[0 % l],
- m_bottom = margins[2 % l];
-
- output += '.modified-emoticon span .emoticon[data-ffz-emote="' + emote.id + '"] {' +
- 'padding:' + m_top + 'px ' + m_right + 'px ' + m_bottom + 'px ' + m_left + 'px;' +
- (emote.shrink_to_fit ? 'max-width: calc(100% - ' + (40 - m_left - m_right - (emote.extra_width || 0)) + 'px);' : '') +
- 'margin: 0 !important' +
- '}\n';
- }
-
- return output +
- (emote.modifier && emote.margins ? '.ffz-bttv .emoticon[data-ffz-emote="' + emote.id + '"] { margin: ' + emote.margins + ' !important;}' : '') +
- '.emoticon[data-ffz-emote="' + emote.id + '"] {' +
- ((emote.margins && ! emote.modifier) ? 'margin:' + emote.margins + ' !important;' : '') +
- (emote.css || '') +
- '}\n';
- },
-
- moderator_css: function(room) {
- if ( ! room.mod_urls )
- return "";
-
- var urls = room.mod_urls,
- image_set = image = 'url("' + urls[1] + '")';
-
- if ( urls[2] || urls[4] ) {
- image_set += ' 1x';
- if ( urls[2] )
- image_set += ', url("' + urls[2] + '") 2x';
- if ( urls[4] )
- image_set += ', url("' + urls[4] + '") 4x';
-
- image_set = WEBKIT + 'image-set(' + image_set + ')';
- }
-
- return '.from-display-preview[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement):not(.colored),' +
- '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement):not(.colored) {' +
- 'background-repeat:no-repeat;' +
- 'background-size:initial !important;' +
- 'background-position:center;' +
- 'background-image: url("' + urls[1] + '") !important;' +
- 'background-image:' + image_set + ' !important}' +
- '.from-display-preview[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement).colored,' +
- '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement).colored {' +
- WEBKIT + 'mask-repeat:no-repeat;' +
- WEBKIT + 'mask-size:initial !important;' +
- WEBKIT + 'mask-position:center;' +
- WEBKIT + 'mask-image: url("' + urls[1] + '");' +
- WEBKIT + 'mask-image:' + image_set + '}';
- },
-
- badge_css: function(badge, klass) {
- klass = klass || ('ffz-badge-' + (badge.real_id || badge.id));
- var urls = badge.urls || {1: badge.image},
- image_set = image = 'url("' + urls[1] + '")';
-
- if ( urls[2] || urls[4] ) {
- image_set += ' 1x';
- if ( urls[2] )
- image_set += ', url("' + urls[2] + '") 2x';
- if ( urls[4] )
- image_set += ', url("' + urls[4] + '") 4x'
-
- image_set = WEBKIT + 'image-set(' + image_set + ')';
- }
-
- var out = '.badges .' + klass + (badge.no_color ? ':not(.colored){' : '{') +
- 'background-color:' + badge.color + ';' +
- (image !== image_set ? 'background-image:' + image + ';' : '') +
- 'background-image:' + image_set + ';' +
- (badge.css || '') + '}' +
-
- '.badges .badge.ffz-badge-replacement.ffz-replacer-' + klass + ':not(.colored){' +
- (image !== image_set ? 'background-image:' + image + ';' : '') +
- 'background-image:' + image_set + '}';
-
- if ( ! badge.no_color )
- out += '.badges .badge.ffz-badge-replacement.ffz-replacer-' + klass + '.colored{' +
- (image !== image_set ? WEBKIT + 'mask-image:' + image + ';' : '') +
- WEBKIT + 'mask-image:' + image_set + '}' +
- '.badges .' + klass + '.colored {' +
- 'background: linear-gradient(' + badge.color + ',' + badge.color + ');' +
- (image !== image_set ? WEBKIT + 'mask-image:' + image + ';' : '') +
- WEBKIT + 'mask-image:' + image_set + ';' +
- (badge.css || '') + '}';
-
- if ( badge.alpha_image )
- out += '.badges .badge.alpha.' + klass + ',' +
- '.ffz-transparent-badges .badges .' + klass + ' {' +
- 'background-image:url("' + badge.alpha_image + '")}';
- return out;
- },
-
- room_badge_css: function(room_id, badge_id, version, data) {
- var img_1x = data.image_url_1x,
- img_2x = data.image_url_2x,
- img_4x = data.image_url_4x,
-
- loyalty = version === Infinity;
-
- return (loyalty ? '.ffz-no-loyalty ' : '') + '.from-display-preview[data-room="' + room_id + '"] .badge.' + badge_id + (loyalty ? '' : '.version-' + version) +
- (loyalty ? ',.ffz-no-loyalty ' : ',') + '.chat-line[data-room="' + room_id + '"] .badge.' + badge_id + (loyalty ? '' : '.version-' + version) + '{' +
- 'background-color:transparent;' +
- 'filter:none;' +
- WEBKIT + 'mask-image: none;' +
- 'background-image:url("' + img_1x + '");' +
- 'background-image:' + WEBKIT + 'image-set(url("' + img_1x + '") 1x' + (img_2x ? ',url("' + img_2x + '") 2x' : '') + (img_4x ? ',url("' + img_4x + '") 4x' : '') + ')}';
- },
-
- cdn_badge_css: function(badge_id, version, data, room) {
- var color = data.color || 'transparent',
- ht = data.has_trans,
- trans_color = data.trans_color || color,
-
- base_image = data.image || ("https://cdn.frankerfacez.com/badges/twitch/" + badge_id + (data.use_svg ? '.svg' : "/" + version + "/")),
- base_trans = ht ? (data.trans_image || ("https://cdn.frankerfacez.com/badges/twitch/" + badge_id + (data.trans_svg ? '.svg' : '/' + version + '/'))) : base_image,
-
- is_svg = base_image.substr(-4) === '.svg',
- trans_svg = base_trans.substr(-4) === '.svg',
-
- image_set = image = 'url("' + base_image + (is_svg ? '' : "1.png") + '")',
- trans_set = trans = 'url("' + base_trans + (trans_svg ? '' : '1_trans.png') + '")',
-
- selector = '.badge.' + badge_id + '.version-' + version + (room ? '[data-room="' + room + '"]' : '');
-
- if ( ! is_svg )
- image_set = WEBKIT + 'image-set(' + image +
- ' 1x, url("' + base_image + (is_svg ? '' : '2.png') +
- '") 2x, url("' + base_image + (is_svg ? '' : '4.png') + '") 4x)';
-
- if ( ! trans_svg )
- trans_set = WEBKIT + 'image-set(' + trans +
- ' 1x, url("' + base_trans + (trans_svg ? '' : '2_trans.png') +
- '") 2x, url("' + base_trans + (trans_svg ? '' : '4_trans.png') + '") 4x)';
-
- return selector + (data.no_color ? '' : ':not(.colored)') + '{' +
- 'background:' + image + ' ' + color + ';' +
- (is_svg ? '}' : 'background-image:' + image_set + '}' ) +
-
- (ht ? '.ffz-transparent-badges ' + selector + '{' +
- 'background:' + trans + ';' +
- (trans_svg ? '}' : 'background-image:' + trans_set + '}') : '') +
-
- (color === 'transparent' && ht ? '.ffz-blank-badges ' + selector + '{' +
- 'background:' + trans_color + '}' : '') +
-
- (data.no_color ? '' : selector + '.colored{' +
- 'background: linear-gradient(' + (ht ? trans_color + ',' + trans_color : color + ',' + color) + ');' +
- (is_svg ? WEBKIT + 'mask-size:18px 18px;' : '') +
- WEBKIT + 'mask-image:' + (ht ? trans : image) + ';' +
- ((ht ? trans_svg : is_svg) ? '}' : WEBKIT + 'mask-image:' + (ht ? trans_set : image_set) + '}')
- );
- },
-
-
- // Render Updating List
-
- render_update_list: function(ffz, segments, container, extra_classes, set_loading) {
- extra_classes = extra_classes ? extra_classes + ' ' : '';
- for(var i=0; i < segments.length; i++) {
- var info = segments[i][1],
- output;
-
- if ( info.type === 'list' )
- output = createElement('ul', extra_classes + 'version-list');
- else if ( info.type === 'text' )
- output = createElement('pre', extra_classes);
- else
- continue;
-
- if ( info.title )
- container.appendChild(createElement('div', 'list-header', info.title));
-
- if ( set_loading )
- output.classList.add('loading');
-
- container.appendChild(output);
-
- var update_content = function(info, output, func) {
- if ( ! document.body.contains(output) )
- return;
-
- var result = info.render.call(ffz);
- if ( ! (result instanceof Promise) )
- result = Promise.resolve(result);
-
- result.then(function(data) {
- if ( set_loading )
- output.classList.remove('loading');
-
- if ( info.type === 'list' ) {
- var handled_keys = [],
- had_keys = output.childElementCount > 0;
-
- for(var i=0; i < data.length; i++) {
- var pair = data[i];
- if ( pair === null ) {
- if ( ! had_keys ) {
- var line = createElement('li', '', '
');
- line.setAttribute('data-key', 'null');
- handled_keys.push('null');
- output.appendChild(line);
- }
- continue;
- }
-
- var key = pair[0], value = pair[1],
- line = output.querySelector('li[data-key="' + key + '"]');
-
- if ( value === null )
- continue;
-
- handled_keys.push(key);
-
- if ( ! line ) {
- line = createElement('li');
- line.setAttribute('data-key', key);
- line.innerHTML = key + '
';
- output.appendChild(line);
- }
-
- line.querySelector('span').innerHTML = value;
- }
-
- var lines = output.querySelectorAll('li');
- for(var i=0; i < lines.length; i++) {
- var line = lines[i];
- if ( handled_keys.indexOf(line.getAttribute('data-key')) === -1 )
- output.removeChild(line);
- }
-
- } else if ( info.type === 'text' ) {
- output.textContent = data;
- }
-
- if ( info.refresh )
- setTimeout(func.bind(ffz, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
-
- }).catch(function(err) {
- ffz.error("Debugging Menu Error", err);
-
- if ( info.type === 'list' )
- output.innerHTML = '
An error occured while updating this information. ';
- else
- output.innerHTML = 'An error occured while updating this information.';
-
- if ( info.refresh )
- setTimeout(func.bind(ffz, info, output, func), typeof info.refresh === "number" ? info.refresh : 1000);
- });
-
- };
-
- update_content.call(ffz, info, output, update_content);
- }
- }
-}
\ No newline at end of file
diff --git a/style.css b/style.css
deleted file mode 100644
index 9b8ed634..00000000
--- a/style.css
+++ /dev/null
@@ -1,4748 +0,0 @@
-/* Fix Tooltip Opacity */
-
-body[data-page^="ember#"] { overflow: hidden; position: relative }
-body > div.tipsy { opacity: 1 !important; }
-body > div.tipsy .tipsy-inner { background-color: rgba(0,0,0,0.8); }
-body > div.tipsy .tipsy-arrow { opacity: 0.8; }
-
-.ffz-flip .drawer__item--brick .warp__logo,
-.ffz-flip .drawer__item--xs .warp__glitch,
-.ffz-flip {
- transform: scale(-1);
-}
-
-.ffz-ui-toggle {
- display: block;
- position: absolute;
- top: 5px; right: 5px;
- height: 18px; width: 24px;
- cursor: pointer;
-}
-
-.app-main.has-sc .dash-dragula { height: calc(100vh - 18rem); }
-.ffz-sidebar-minimize .app-main.has-sc .dash-dragula { height: calc(100vh - 14rem) }
-
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] .app-main.has-sc { top: 0 }
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #main_col { margin: 0 !important }
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] .dash-dragula { height: calc(100vh - 70px) }
-.ffz-minimize-conversations.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] .dash-dragula { height: calc(100vh - 50px) }
-
-
-.ffz-hide-thumb-info-on-hover .stream.item:hover .boxart,
-.ffz-hide-thumb-info-on-hover .stream.item:hover .overlay_info,
-.ffz-hide-thumb-info-on-hover .stream.item:hover .card__meta,
-.ffz-hide-thumb-info-on-hover .video.item:hover .boxart,
-.ffz-hide-thumb-info-on-hover .video.item:hover .overlay_info,
-.ffz-hide-thumb-info-on-hover .video.item:hover .card__meta,
-.ffz-hide-thumb-info-on-hover .video.item:hover .progress-bar-wrapper,
-.ffz-hide-thumb-info-on-hover .card-carousel__item:hover .card__boxpin,
-.ffz-hide-thumb-info-on-hover .ffz-directory-preview:hover .card__boxpin,
-
-.ffz-minimal-chat-input .trending-emote-feed,
-.ffz-minimal-chat-input .trending-emote-interface,
-.ffz-hide-trending-emotes .trending-emote-feed,
-.ffz-hide-trending-emotes .trending-emote-interface,
-.player[data-paused="false"] #js-follow-panel,
-body:not(.ffz-theater-basic-stats) .player-userinfo__game,
-body:not(.ffz-theater-basic-stats) .player-controls-top,
-.moderation-card__actions:not(.loading):empty,
-.ffz-hide-purchase-game .cmrc-channel-box,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] .top-nav,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_col,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_close,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #right_col,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #right_close,
-.ffz-minimal-dashboard[data-current-path="user.dashboards.index"] .new-dashboard .directory_header,
-.ffz-hide-directory-uploads .ffz-following-uploads,
-.pinned-cheers__dismiss,
-.ffz-hide-pinned-cheers .pinned-cheers,
-.cn-hosting--bottom .ffz-channel-options .balloon:after,
-.ffz-host-info .card__layout:not(.ffz),
-.ffz-following-row[data-loaded="false"] .button,
-.ffz-following-row[data-loaded="false"] .switch,
-.ffz-following-row[data-following="false"] .switch,
-.ffz-moderation-card:not(.lv-notes) ul.menu li[data-page="notes"],
-.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="history"],
-.ffz-moderation-card:not(.lv-tabs) ul.menu li.needs-lv,
-.ffz-hide-prime .drawer .warp__list.js-offers,
-.ffz-hide-prime-collapsed .drawer.closed .js-offers,
-.ffz-hide-friends-collapsed .drawer.closed .friend-list,
-.ffz-minimal-channel-title .cn-metabar > div:first-child:not(:hover) .js-card__info,
-.ffz-minimal-channel-title .cn-metabar__title:not(:hover) .card__info,
-.ffz-minimal-channel-bar .cn-bar-spacer,
-.ffz-channel-bar-bottom .cn-bar-spacer,
-.ffz-hide-channel-banner .cn-cover-wrap,
-.app-main .ad_leader:empty,
-body:not(.ffz-show-bits-tags) .bits-tag--container,
-.ffz-hide-socialbar .social-column,
-.ffz-hide-socialbar .has-sc #left_close,
-.ffz-hide-friends .friend-button,
-.ffz-hide-friends .sc-online-friends,
-.ffz-hide-friends .sc-suggestions,
-.ffz-hide-friends .sc-search,
-.ffz-hide-friends .top-nav-drawer__status,
-.ffz-hide-friends nav .friend-list,
-.ffz-hide-friends .js-presence-indicator,
-.ffz-hide-friends #friend-requests-in-notifications,
-.ffz-hide-more-at-twitch nav .tse-content > .warp__list:last-of-type,
-.ffz-hide-recommended-friends .recommended-friends,
-.ffz-hide-recommended-friends .sc-suggestions,
-.ffz-hide-recommended-channels .js-recommended-channels,
-.drawer.closed .js-recommended-channels .warp__item--header,
-.drawer.closed .promotedGames .warp__item--header,
-.ffz-hide-promoted-games .promotedGames,
-.ffz-hide-recent-past-broadcast .recent-past-broadcast,
-.ffz-hide-view-count .stat.twitch-channel-views,
-.ffz-hide-view-count .cn-metabar__viewcount,
-.ffz-minimal-chat-input .chat-interface .emoticon-selector-toggle,
-.ffz-menu-replace .chat-interface .ember-emoticon-selector,
-.ffz-menu-replace .chat-interface .emoticon-selector-toggle {
- display: none !important;
-}
-
-.app-main.theatre.has-sc.has-net-neutrality,
-.ffz-hide-pinned-cheers .chat-messages { top: 0 !important }
-
-.ffz-channel-bar-bottom #channel {
- margin-bottom: 60px;
-}
-
-.ffz-channel-bar-bottom .notification-controls .balloon {
- top: auto;
- bottom: 50px;
-}
-
-.ffz-channel-bar-bottom .js-username-hover-tip .balloon {
- top: auto;
- bottom: 100px;
-}
-
-.ffz-channel-bar-bottom .subscription-modal__balloon {
- top: -400px !important;
-}
-
-.cn-hosting--bottom .ffz-channel-options .balloon {
- left: 0;
- right: auto;
-}
-
-.ffz-channel-bar-bottom .cn-bar-fixed {
- top: auto;
- bottom: 0;
- box-shadow: inset 0 1px 0 #e5e3e8,0 -1px -1px rgba(0,0,0,.065);
-}
-
-.ffz-dark.ffz-channel-bar-bottom .cn-bar-fixed {
- box-shadow: inset 0 1px 0 #161616, -1px -1px rgba(255,255,255,0.065);
-}
-
-body:not(.ffz-show-bits-tags) .ember-chat .chat-messages.bits-tags__offset {
- top: 0;
-}
-
-.ffz-hide-trending-emotes .chat-room.trending-emotes-enabled .chat-messages .chat-line,
-.ffz-minimal-chat-input .chat-room.trending-emotes-enabled .chat-messages .chat-line {
- padding-right: 20px;
-}
-
-.ffz-hide-trending-emotes .chat-room.trending-emotes-enabled .chat-messages { bottom: 111px }
-.ffz-hide-trending-emotes .chat-room.trending-emotes-enabled .chat-interface { height: 111px }
-
-body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + .ffz-ui-toggle svg,
-body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + div + .ffz-ui-toggle svg
-{
- height: 14px;
- width: 18px;
-}
-
-body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + .ffz-ui-toggle,
-body:not(.ffz-minimal-chat-input):not(.ffz-menu-replace) .chat-interface .emoticon-selector-toggle + div + .ffz-ui-toggle {
- height: 14px;
- width: 18px;
- top: 28px;
- right: 6px;
-}
-
-.ffz-ui-toggle svg.svg-emoticons path { fill: rgba(0,0,0,0.2); }
-.ffz-ui-toggle:hover svg.svg-emoticons path { fill: rgba(0,0,0,0.5); }
-
-.card__img .overlay_info.length.live svg path,
-.card-carousel__item .card__img .overlay_info.live svg path,
-.streams .stream .content .overlay_info.live svg path,
-.videos .video .content .overlay_info.live svg path { fill: #ff2020; }
-
-.theme--dark .chat-container .ffz-ui-toggle svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,
-.app-main.theatre .ffz-ui-toggle svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle svg.svg-emoticons path { fill: #555; }
-
-.theme--dark .chat-container .ffz-ui-toggle:hover svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,
-.app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle:hover svg.svg-emoticons path { fill: #999; }
-
-
-.ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: rgba(80,0,0,0.2); }
-.ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path { fill: rgba(80,0,0,0.5); }
-
-.ffz-ui-toggle.live svg.svg-emoticons path { fill: rgba(100,65,165,0.5); }
-.ffz-ui-toggle.live:hover svg.svg-emoticons path { fill: rgba(100,65,165,1); }
-
-.ffz-ui-toggle.blue.live svg.svg-emoticons path { fill: rgba(47,88,185,0.5); }
-.ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: rgba(47,88,185,1); }
-
-.app-main.theatre .ffz-ui-toggle.news svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.news svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.news svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.news svg.svg-emoticons path,
-.ffz-ui-toggle.news svg.svg-emoticons path { fill: rgba(117, 80, 0, 0.5); }
-
-.app-main.theatre .ffz-ui-toggle.news:hover svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.news:hover svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.news:hover svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.news:hover svg.svg-emoticons path,
-.ffz-ui-toggle.news:hover svg.svg-emoticons path { fill: rgba(117, 80, 0, 0.8); }
-
-.app-main.theatre .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: #453434; }
-
-.app-main.theatre .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path { fill: #543f3f; }
-
-.app-main.theatre .ffz-ui-toggle.live svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.live svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.live svg.svg-emoticons path { fill: #513c78; }
-
-.app-main.theatre .ffz-ui-toggle.live:hover svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.live:hover svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.live:hover svg.svg-emoticons path { fill: #5b4487; }
-
-.app-main.theatre .ffz-ui-toggle.blue.live svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.blue.live svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.blue.live svg.svg-emoticons path { fill: #3c4e78; }
-
-.app-main.theatre .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
-.theme--dark .chat-container .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
-.chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
-body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: #445887; }
-
-
-.ffz-ui-toggle.live {
- animation: ffzfade 8s linear infinite;
-}
-
-@keyframes ffzfade {
- from, to { opacity: 1; }
- 50% { opacity: 0.75; }
-}
-
-.warp__logo:hover {
- animation: ffzflipwobble 4s infinite;
-}
-
-@keyframes ffzflipwobble {
- 0%,30% { transform: none }
- 36% { transform: rotate(185deg) }
- 38% { transform: rotate(175deg) }
- 40%,90% { transform: rotate(180deg) }
- 96% { transform: rotate(365deg) }
- 98% { transform: rotate(355deg) }
- 100% { transform: rotate(360deg) }
-}
-
-
-.ember-chat .chat-menu.ffz-ui-popup { padding: 0 }
-
-.ffz-button {
- float: right;
- margin-top: -6px;
- text-transform: none;
-}
-
-.ffz-noty .noty_message {
- background-image: url("//cdn.frankerfacez.com/icon32.png") !important;
- background-repeat: no-repeat !important;
- background-position: 5px 10px !important;
- padding-left: 42px !important;
- text-align: left;
-}
-
-.ffz-ui-popup .button.live { overflow: hidden; background: #6441A5; color: #fff; }
-.ffz-ui-popup .button.live span { z-index: 2; position: relative; }
-
-.ffz-ui-popup .button.live:before, .ffz-ui-popup .button.live:after {
- content: "";
- display: block;
- position: absolute;
- width: 0px;
- height: 0px;
- border-radius: 999px;
- top: 50%;
- left: 50%;
- z-index: 1
-}
-
-.ffz-ui-popup .button.live.purple:before, .ffz-ui-popup .button.live.purple:after {
- box-shadow: 0 0 3px 1px #a68ed2,inset 0 0 3px 1px #a68ed2;
- -moz-box-shadow: 0 0 3px 1px #a68ed2,inset 0 0 3px 1px #a68ed2;
- -webkit-box-shadow: 0 0 3px 1px #a68ed2,inset 0 0 3px 1px #a68ed2;
-}
-
-.ffz-ui-popup .button.live.blue:before, .ffz-ui-popup .button.live.blue:after {
- box-shadow: 0 0 3px 1px #8ea2d1,inset 0 0 3px 1px #8ea2d1;
- -moz-box-shadow: 0 0 3px 1px #8ea2d1,inset 0 0 3px 1px #8ea2d1;
- -webkit-box-shadow: 0 0 3px 1px #8ea2d1,inset 0 0 3px 1px #8ea2d1;
-}
-
-.ffz-ui-popup .button.live:before {
- animation: expand 1500ms infinite ease-in;
-}
-
-.ffz-ui-popup .button.live:after {
- animation: expand 1500ms infinite 750ms ease-in;
-}
-
-#dash_main #stats .stat.dark#ffz_count svg path { fill: #cacaca; }
-
-.ffz-live-team-channel .ffz-game {
- display: inline-block;
- max-width: 150px;
- text-overflow: ellipsis;
- overflow: hidden;
- margin-bottom: -5px;
-}
-
-
-/* Minimal Channel Title */
-
-.ffz-minimal-channel-title .cn-metabar > div:first-child:hover,
-.ffz-minimal-channel-title .cn-metabar__title:hover {
- background-color: #fff;
- padding: 1rem;
- margin-left: -1rem;
-}
-
-.ffz-dark.ffz-minimal-channel-title .cn-metabar > div:first-child:hover,
-.ffz-dark.ffz-minimal-channel-title .cn-metabar__title:hover { background-color: #101010 }
-
-.ffz-minimal-channel-title .cn-metabar > div:first-child:not(:hover) h3 {
- margin-bottom: 0 !important;
-}
-
-.ffz-minimal-channel-title .cn-metabar > div:first-child:not(:hover) .cn-metabar__boxart,
-.ffz-minimal-channel-title .cn-metabar__title:not(:hover) .card__img--boxart {
- height: 20px !important;
- width: 14px !important;
-}
-
-
-/* Theater Mode hover bar */
-
-.ffz-theater-basic-stats .theatre .player-userinfo__game,
-.ffz-theater-basic-stats .theatre .player-controls-top {
- display: block
-}
-
-.ffz-theater-stats .theatre #main_col:hover .cn-hosting--bottom,
-.ffz-theater-stats .theatre #main_col:focus .cn-hosting--bottom,
-.ffz-theater-stats .theatre .cn-hosting--bottom:hover,
-.ffz-theater-stats .theatre .cn-hosting--bottom:focus,
-
-.ffz-theater-stats .theatre #main_col:hover .cn-metabar__more,
-.ffz-theater-stats .theatre #main_col:focus .cn-metabar__more,
-.ffz-theater-stats .theatre .cn-metabar__more:hover,
-.ffz-theater-stats .theatre .cn-metabar__more:focus {
- background-color: rgba(25,25,31,0.95);
-
- color: #aaa;
-
- position: fixed;
- left: 10px;
-
- width: auto;
-
- z-index: 7;
- padding: 10px !important;
- flex-wrap: wrap;
-}
-
-.ffz-theater-stats .theatre .cn-hosting--bottom,
-.ffz-theater-stats .theatre .cn-metabar__more {
- bottom: 85px;
-}
-
-.ffz-theater-stats:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .theatre .cn-hosting--bottom,
-.ffz-theater-stats:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .theatre .cn-metabar__more {
- bottom: 95px;
-}
-
-.ffz-theater-stats .theatre .cn-metabar__more span,
-.ffz-theater-stats .theatre .cn-metabar__more div { flex-grow: 0 !important }
-
-.ffz-theater-stats .theatre .channel-stats .stat { color: #aaa; }
-
-.ffz-theater-stats .theatre .channel-stats span:not(.live-count) svg path {
- fill: rgba(255,255,255,0.35);
-}
-
-.ffz-theater-stats .theatre .follow-button .notify:before,
-.ffz-theater-stats .theatre .button.drop:after,
-.ffz-theater-stats .theatre .follow-button .drop.follow:after {
- border: 5px solid rgba(255,255,255,0.35);
- border-left-color: transparent;
- border-right-color: transparent;
- border-bottom-color: transparent;
-}
-
-.ffz-theater-stats .theatre .follow-button .notify {
- background-color: #25252a;
-}
-
-.ffz-theater-stats .theatre .button.button--icon-only:not(.follow-button) svg path {
- fill: #a68ed2;
-}
-
-.theme--dark .chat-container .chat-line .button,
-.chat-container.dark .chat-line .button,
-.ffz-theater-stats .theatre .button.primary.subscribe-button {
- color: #fff;
-}
-
-
-/* SRL Race Support */
-
-#ffz-ui-host-button { vertical-align: middle }
-
-.srl-logo {
- display: inline-block;
- width: 34px;
- height: 16px;
- background-image: url("//cdn.frankerfacez.com/script/srl_button.png");
-}
-
-.cn-metabar__ffz .srl-logo {
- vertical-align: middle;
- margin-top: -2px;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] {
- background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
- background-repeat: no-repeat;
- padding: 1rem;
- background-position: 115% 110%;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading {
- margin: -1rem -1rem 1rem;
- height: 65px;
- position: relative;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading div {
- padding: 0 1rem;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading h2,
-#ffz-metadata-popup[data-key="srl_race"] .heading .goal {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading .goal {
- display: block;
- margin: calc(-1rem + 1px);
- padding: calc(1rem - 1px);
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover {
- white-space: normal;
- background-color: rgba(255,255,255,0.9);
- border-bottom: 1px solid rgba(0,0,0,0.2);
-}
-
-.theatre #ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover,
-.ffz-dark #ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover {
- background-color: rgba(16,16,16,0.9);
- border-color: rgba(255,255,255,0.2);
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading h2 {
- max-width: 240px;
- font-size: 1.5em;
- padding-bottom: 5px;
- display: block;
- width: 200px;
- margin-bottom: 0;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .heading > span {
- line-height: 30px;
- position: absolute;
- top: 7.5px;
- right: 1rem;
- padding: 0 5px;
- background: rgba(0,0,0,0.5);
- color: #fff;
- border-radius: 5px;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] p {
- float: right;
- margin-bottom: 0;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .right {
- padding-top: 0;
- text-align: right;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] .table {
- overflow-y: auto;
- border-bottom: 1px solid;
- margin-bottom: 1rem;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] table {
- width: 100%;
- text-align: center;
- border-spacing: 0;
-}
-
-#ffz-metadata-popup[data-key="srl_race"] table a {
- color: inherit;
-}
-
-.ffz-about-table a.twitch,
-.ffz-about-table a.youtube,
-.ffz-about-table a.twitter,
-#ffz-metadata-popup[data-key="srl_race"] a.twitch,
-#ffz-metadata-popup[data-key="srl_race"] a.hitbox {
- display: inline-block;
- height: 16px;
- margin-left: 5px;
- background-repeat: no-repeat;
-}
-
-.ffz-about-table a.youtube {
- width: 23px;
- background-image: url("//cdn.frankerfacez.com/script/youtube_logo.png");
-}
-
-.ffz-about-table a.twitter {
- width: 20px;
- background-image: url("//cdn.frankerfacez.com/script/twitter_logo.png");
-}
-
-#ffz-metadata-popup[data-key="srl_race"] a.twitch,
-.ffz-about-table a.twitch {
- width: 15px;
- background-image: url("//cdn.frankerfacez.com/script/twitch_logo.png");
-}
-
-#ffz-metadata-popup[data-key="srl_race"] a.hitbox {
- width: 12px;
- background-image: url("//cdn.frankerfacez.com/script/hitbox_logo.png");
-}
-
-#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+1) td { background-color: rgba(255,255,0,.2); }
-#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+2) td { background-color: rgba(128,128,128,.2); }
-#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+3) td { background-color: rgba(210,100,0,.2); }
-#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="forfeit"] td { opacity: 0.5; background-color: rgba(210,100,100,.2); }
-#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="racing"] td.time { opacity: 0.5; }
-
-#ffz-metadata-popup[data-key="srl_race"] table th, #ffz-metadata-popup[data-key="srl_race"] td { padding: 1px; }
-#ffz-metadata-popup[data-key="srl_race"] table th { border-bottom: 1px solid; }
-
-
-/* Dark Menu */
-
-.ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before {
- background-color: rgba(255,255,255,0.5);
- display: block;
- width: 100%;
- height: 100%;
- padding: 0;
- background-position: calc(100% - 2.5px) calc(100% - 2.5px);
-}
-
-.ffz-dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
-.theatre .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
-.theme--dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before,
-.dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon.locked:before {
- background-color: rgba(16,16,16,0.5);
-}
-
-.ffz-sub-button + .ffz-sub-button { border-left: 1px solid #ffffff }
-
-.ffz-dark .ffz-sub-button + .ffz-sub-button,
-.theatre .ffz-sub-button + .ffz-sub-button,
-.theme--dark .ffz-sub-button + .ffz-sub-button,
-.dark .ffz-sub-button + .ffz-sub-button { border-left-color: #101010 }
-
-.emoticon.locked.unlocked:before { display: none !important }
-
-
-#ffz-chat-menu { background-color: transparent !important; }
-
-.ffz-dark .ember-chat .chat-menu .list-header,
-.theatre .ember-chat .chat-menu .list-header,
-.theme--dark .ember-chat .chat-menu .list-header,
-.dark .ember-chat .chat-menu .list-header {
- border-top-color: rgba(255,255,255, 0.2);
-}
-
-.ffz-dark .ffz-ui-menu-page .ffz-filter-container,
-.theatre .ffz-ui-menu-page .ffz-filter-container,
-.theme--dark .ffz-ui-menu-page .ffz-filter-container,
-.dark .ffz-ui-menu-page .ffz-filter-container,
-
-.ffz-dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-.theatre .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-.theme--dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-.dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-
-.ffz-dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading,
-.theatre .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading,
-.theme--dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading,
-.dark .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading,
-
-.ffz-dark .ffz-ui-popup ul.menu,
-.theatre .ffz-ui-popup ul.menu,
-.theme--dark .ffz-ui-popup ul.menu,
-.dark .ffz-ui-popup ul.menu,
-
-.ffz-dark .ffz-ui-popup ul.menu a,
-.theatre .ffz-ui-popup ul.menu a,
-.theme--dark .ffz-ui-popup ul.menu a,
-.dark .ffz-ui-popup ul.menu a,
-
-.ffz-sidebar-swap.ffz-dark .ffz-ui-popup ul.menu a,
-.ffz-sidebar-swap .theatre .ffz-ui-popup ul.menu a,
-.ffz-sidebar-swap .theme--dark .ffz-ui-popup ul.menu a,
-.ffz-sidebar-swap .dark .ffz-ui-popup ul.menu a {
- border-color: #32323e;
-}
-
-.ffz-dark .ffz-ui-popup ul.menu,
-.theatre .ffz-ui-popup ul.menu,
-.theme--dark .ffz-ui-popup ul.menu,
-.dark .ffz-ui-popup ul.menu {
- background-color: #212121;
-}
-
-.ffz-dark .ffz-ui-popup ul.menu li.active,
-.theatre .ffz-ui-popup ul.menu li.active,
-.dark .ffz-ui-popup ul.menu li.active,
-.theme--dark .ffz-ui-popup ul.menu li.active {
- background-color: #101010;
-}
-
-.ffz-dark .ffz-ui-popup ul.menu li.active a,
-.theatre .ffz-ui-popup ul.menu li.active a,
-.dark .ffz-ui-popup ul.menu li.active a,
-.theme--dark .ffz-ui-popup ul.menu li.active a {
- border-top-color: #101010;
-}
-
-.ffz-dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.theatre .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.theme--dark .ffz-ui-popup.emoticon-selector .emoticon-selector-box {
- background-color: #101010;
- color: #c3c3c3;
- border-color: #32323e;
-}
-
-
-
-/* Menu Options */
-
-.emoticon-selector .emoticon-selector-box .subscribe-message {
- padding: 10px 20px;
- margin: 0;
-}
-
-.emoticon-selector .emoticon-grid.ffz-no-emotes img {
- padding: 5px;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
-}
-
-.ffz-ui-sub-menu-page[data-page="favorites"] .emoticon-grid.ffz-no-emotes {
- padding: 10px 20px;
-}
-
-.suggestion.ffz-favorite,
-.emoticon.ffz-favorite { position: relative; }
-
-.favorites-grid:not(:empty) + .ffz-no-emotes,
-.favorites-grid .emoticon.ffz-favorite:before { display: none }
-
-.tipsy .ffz-favorite,
-.suggestion.ffz-favorite:before,
-.emoticon.ffz-favorite:before {
- content: "";
- display: block;
- height: 14px; width: 14px;
- position: absolute;
- right: 0; bottom: 0;
- background: url("//cdn.frankerfacez.com/script/emoticon_favorite.png") no-repeat;
-}
-
-.tipsy .ffz-favorite { right: 10px; bottom: 10px }
-
-.suggestion.ffz-favorite:before { right: 2.5px; bottom: 2.5px }
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.menu-side-padding { padding-left: 20px; padding-right: 20px; }
-
-.emoticon-grid.collapsable.collapsed span,
-.chat-menu-content.collapsable.collapsed p { display: none; }
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.collapsable.collapsed .heading,
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsable.collapsed .heading {
- padding-bottom: 0;
-}
-
-.emoticon-grid.collapsable .heading,
-.emoticon-grid.collapsable.collapsed,
-.chat-menu-content.collapsable.collapsed,
-.chat-menu-content.collapsable .heading {
- cursor: pointer;
-}
-
-.emoticon-grid.collapsable .heading,
-.chat-menu-content.collapsable .heading {
- position: relative;
-}
-
-.pin-switch {
- display: block;
- height: 16px;
- padding: 2px;
- margin: 0 !important;
- cursor: pointer;
-}
-
-.pin-switch:hover svg path,
-.pin-switch.active svg path { fill: #14b866 !important }
-.pin-switch.active:hover svg path { fill: #fc3636 !important }
-
-.pin-switch svg path { fill: rgba(0,0,0,0.2); }
-
-.dark .pin-switch svg path,
-.theatre .dark .pin-switch svg path,
-.theme--dark .pin-switch svg path,
-.theatre .theme--dark .pin-switch svg path { fill: rgba(255,255,255,0.2) }
-
-.pin-switch,
-.list-header time,
-.list-header span.right { float: right; }
-
-.ember-chat .chat-menu .list-header.sub-header {
- border-top: none;
- margin: 0 20px;
- padding: 5px 0 0;
-}
-
-.ember-chat .chat-menu .list-header.sub-header + .chat-menu-content {
- padding-top: 0;
-}
-
-.chat-menu-content.collapsable .heading span.right {
- padding-right: 15px;
-}
-
-.emoticon-grid.collapsable .heading:before,
-.chat-menu-content.collapsable .heading:before {
- content: "";
- border: 5px solid #666;
- border-left-color: transparent;
- border-right-color: transparent;
-
- position: absolute;
- margin-top: 6px;
- right: 20px;
-}
-
-.emoticon-grid.collapsable.collapsed .heading:before,
-.chat-menu-content.collapsable.collapsed .heading:before { border-bottom-color: transparent; }
-
-.emoticon-grid.collapsable:not(.collapsed) .heading:before {
- display: none;
-}
-
-.chat-menu-content.collapsable:not(.collapsed) .heading:before {
- border-top-color: transparent;
- margin-top: 1px;
-}
-
-
-#small_nav .content ul li#ffz_small_menu .filter_icon svg {
- margin: 11px 13px;
-}
-
-.ffz-player-reset svg { padding: 0 3px }
-
-.player-menu__menu,
-.ffz-ui-sub-menu-page,
-.ffz-ui-menu-page { overflow-y: auto; }
-
-.ffz-ui-menu-page[data-page="about"] .ffz-ui-sub-menu-page .chat-menu-content:first-child {
- padding: 20px 0 10px;
-}
-
-.ffz-ui-sub-menu-page[data-page="about"],
-.ffz-ui-menu-page .chat-menu-content p { padding: 0 20px; }
-
-.ffz-moderation-card .version-list span,
-.ffz-ui-menu-page .version-list span { float: right }
-
-.ffz-moderation-card .version-list li:not(:last-of-type),
-.ffz-ui-menu-page .version-list li:not(:last-of-type) {
- border-bottom: 1px dotted rgba(127,127,127,0.5);
-}
-
-.ffz-ui-menu-page .version-list ul {
- margin-left: 20px
-}
-
-.ffz-ui-menu-page pre {
- box-shadow: none !important;
- white-space: pre-wrap;
-}
-
-#ffz-old-news-button {
- text-align: center;
- text-transform: none;
- padding-bottom: 15px;
-}
-
-#ffz-old-news { display: none }
-
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.theme--dark .chat-container .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
-.chat-container.dark .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box {
- background-color: rgb(16,16,16);
- color: rgb(195,195,195);
- border-color: #32323e;
-}
-
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading {
- padding: 10px 20px;
- border-top: 1px solid rgba(0,0,0,0.2);
- text-align: left;
-}
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading {
- padding-left: 43px;
- background-repeat: no-repeat;
- background-position: 20px 10px;
-}
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid + .emoticon-grid { padding-top: 0; }
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content:first-of-type .heading,
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid:first-of-type .heading {
- border-top: none;
- padding-top: 0;
- background-position: 20px 0;
-}
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content {
- padding: 10px 0;
- background-color: transparent;
-}
-
-.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content + .chat-menu-content {
- padding-top: 0;
-}
-
-.ffz-ui-menu-page span.help {
- display: block;
- opacity: 0.75;
-}
-
-.ffz-ui-menu-page p.disabled span.switch-label,
-.ffz-ui-menu-page span.help,
-.ffz-ui-menu-page span.option-label,
-.ffz-ui-menu-page p.option a {
- margin-left: 50px;
-}
-
-.ffz-ui-menu-page span.option-label {
- line-height: 25px;
-}
-
-.ffz-ui-menu-page input,
-.ffz-ui-menu-page select {
- margin: 0 10px 5px;
-}
-
-.ffz-ui-menu-page input[type="file"] {
- width: auto;
-}
-
-#ffz-chat-menu { pointer-events: none; }
-
-.ffz-ui-popup ul.menu {
- list-style-type: none;
- border-top: 1px solid rgba(0,0,0,0.2);
- background-color: #eee;
- margin: 0 1px 1px;
-}
-
-.ffz-ui-popup ul.menu:not(.sub-menu) {
- cursor: ew-resize;
-}
-
-.ffz-ui-popup .emoticon-selector-box {
- width: 10000px !important; /* Max-width has our back */
- max-width: 300px;
- pointer-events: auto;
-}
-
-.ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon-grid { background-color: transparent !important; }
-
-.ffz-bttv-dark .ffz-ui-popup ul.menu {
- background-color: #282828;
-}
-
-.ffz-ui-popup ul.sub-menu li.title,
-.ffz-ui-menu-page .heading .right,
-.ffz-ui-popup ul.menu li.item {
- float: right;
-}
-
-.ffz-ui-popup ul.sub-menu li.item,
-.ffz-ui-popup ul.menu li.title {
- float: left;
-}
-
-.ffz-ui-popup ul.sub-menu {
- background-color: #dfdfdf;
- margin: 0 1px;
-}
-
-.app-main.theatre .ffz-ui-popup ul.sub-menu,
-.theme--dark .chat-container .ffz-ui-popup ul.sub-menu,
-.chat-container.dark .ffz-ui-popup ul.sub-menu,
-body.ffz-bttv-dark .ffz-ui-popup ul.sub-menu {
- background-color: #181818;
-}
-
-.ffz-ui-popup ul.sub-menu a {
- text-decoration: none;
- color: #333;
-}
-
-.app-main.theatre .ffz-ui-popup ul.sub-menu a,
-.theme--dark .chat-container .ffz-ui-popup ul.sub-menu a,
-.chat-container.dark .ffz-ui-popup ul.sub-menu a,
-body.ffz-bttv-dark .ffz-ui-popup ul.sub-menu a {
- color: #d3d3d3 !important;
-}
-
-span.ffz-handle {
- display: inline-block;
- position: relative;
- height: 26px;
- width: 14px;
- transition: width 500ms;
-}
-
-span.ffz-handle:before,
-span.ffz-handle:after {
- position: absolute;
- left: 4px;
- top: 5px;
- content: "";
- height: 14px;
- border: 1px solid #bbb;
- border-radius: 4px;
- transition: transform 500ms, left 500ms, border-color 500ms, border-width 500ms, height 500ms;
-}
-
-span.ffz-handle:after { left: 8px }
-
-.ui-moved span.ffz-handle { width: 24px; cursor: pointer; }
-
-.ui-moved span.ffz-handle:before,
-.ui-moved span.ffz-handle:after {
- left: 11px;
- border-color: #333;
-}
-
-.ui-moved span.ffz-handle:before { transform: rotate(45deg); }
-.ui-moved span.ffz-handle:after { transform: rotate(-45deg); }
-
-.ffz-dark .ffz-recent-expando span.ffz-handle:before,
-.ffz-dark .ffz-recent-expando span.ffz-handle:after,
-
-.app-main.theatre span.ffz-handle:before,
-.theme--dark .chat-container span.ffz-handle:before,
-.chat-container.dark span.ffz-handle:before,
-body.ffz-bttv-dark span.ffz-handle:before,
-.app-main.theatre span.ffz-handle:after,
-.theme--dark .chat-container span.ffz-handle:after,
-.chat-container.dark span.ffz-handle:after,
-body.ffz-bttv-dark span.ffz-handle:after {
- border-color: #666;
-}
-
-.app-main.theatre .ui-moved span.ffz-handle:before,
-.theme--dark .chat-container .ui-moved span.ffz-handle:before,
-.chat-container.dark .ui-moved span.ffz-handle:before,
-body.ffz-bttv-dark .ui-moved span.ffz-handle:before,
-.app-main.theatre .ui-moved span.ffz-handle:after,
-.theme--dark .chat-container .ui-moved span.ffz-handle:after,
-.chat-container.dark .ui-moved span.ffz-handle:after,
-body.ffz-bttv-dark .ui-moved span.ffz-handle:after {
- border-color: #d3d3d3;
-}
-
-
-.ffz-recent-expando > span.ffz-handle {
- float: left;
- margin: -3px 5px -3px -5px;
-}
-
-.ffz-ui-popup ul.menu li.title > span.ffz-handle {
- float: left;
- margin: 5px;
-}
-
-.ffz-ui-popup ul.menu li.title > span.title {
- display: block;
- margin-left: 24px;
- padding: 10px 20px 10px 0;
- line-height: 16px;
- transition: margin-left 500ms;
-}
-
-.ffz-ui-popup.ui-moved ul.menu li.title > span.title { margin-left: 34px; }
-
-.ffz-ui-popup ul.menu a {
- display: block;
- padding: 10px;
- height: 16px;
- margin-top: -1px;
- cursor: pointer;
- border-left: 1px solid rgba(0,0,0,0.2);
- border-top: 1px solid transparent;
-}
-
-.ffz-ui-popup ul.sub-menu a {
- border-left: none;
- border-right: 1px solid rgba(0,0,0,0.2);
-}
-
-body.ffz-bttv-dark .ffz-ui-popup ul.menu { border-top-color: rgba(255,255,255,0.1) }
-body.ffz-bttv-dark .ffz-ui-popup ul.menu a { border-left-color: rgba(255,255,255,0.1) }
-body.ffz-bttv-dark .ffz-ui-popup ul.sub-menu a { border-right-color: rgba(255,255,255,0.1) }
-
-.ffz-ui-popup ul.menu li.active {
- background-color: #fff;
-}
-
-.ffz-ui-popup ul.menu li.active a {
- border-top-color: #fff;
-}
-
-.ffz-ui-popup ul.menu li.active.has-sub-menu {
- background-color: #dfdfdf;
-}
-
-.ffz-ui-popup ul.menu li.active.has-sub-menu a {
- border-top-color: #dfdfdf;
-}
-
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup ul.menu li.active,
-.chat-container.dark .chat-interface .ffz-ui-popup ul.menu li.active,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup ul.menu li.active,
-body.ffz-bttv-dark .ffz-ui-popup ul.menu li.active {
- background-color: rgb(16,16,16);
-}
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup ul.menu li.active a,
-.chat-container.dark .chat-interface .ffz-ui-popup ul.menu li.active a,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup ul.menu li.active a,
-body.ffz-bttv-dark .ffz-ui-popup ul.menu li.active a {
- border-top-color: rgb(16,16,16);
-}
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu,
-.chat-container.dark .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu,
-body.ffz-bttv-dark .ffz-ui-popup ul.menu li.active.has-sub-menu {
- background-color: #181818;
-}
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu a,
-.chat-container.dark .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu a,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup ul.menu li.active.has-sub-menu a,
-body.ffz-bttv-dark .ffz-ui-popup li.active.has-sub-menu a {
- border-top-color: #181818;
-}
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup a,
-.chat-container.dark .chat-interface .ffz-ui-popup a,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup a,
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page a { color: #fff; }
-
-
-.theme--dark .chat-container .chat-interface .ffz-ui-popup ul.menu svg path,
-.chat-container.dark .chat-interface .ffz-ui-popup ul.menu svg path,
-.app-main.theatre .chat-container .chat-interface .ffz-ui-popup ul.menu svg path,
-.ffz-dark .ffz-ui-popup ul.menu svg path,
-body.ffz-bttv-dark .ffz-ui-popup ul.menu svg path { fill: #d3d3d3; }
-
-.ffz-ui-popup ul.menu svg path { fill: #333; }
-
-
-.ffz-ui-popup .option-label span,
-.ffz-ui-popup .switch-label span {
- opacity: 0.8;
- font-size: 10px;
- line-height: 20px;
- padding: 4px;
- background: rgba(0,0,0,0.2);
- vertical-align: top;
-}
-
-/* BTTV Menu Fixes */
-
-.emote-menu .header-info img { max-width: 18px; max-height: 18px }
-
-.bttv-incompatibility {
- padding-top: 8px !important;
-}
-
-body.ffz-bttv-dark .ffz-ui-popup .emoticon-grid .heading,
-body.ffz-bttv-dark .ffz-ui-popup li.title { color: #fff; }
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page { background-color: #101010; }
-.ffz-bttv .ffz-ui-popup .chat-menu-content p a {
- padding: 0;
-}
-
-body.ffz-bttv-dark .ffz-ui-popup ul.menu,
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page { margin: 0 }
-
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading,
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading {
- border-color: rgba(255,255,255,0.1);
-}
-
-body.ffz-bttv-dark .ffz-ui-popup ul.menu:not(.sub-menu),
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page { border: 1px solid rgba(255,255,255,0.1) }
-body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page { border-bottom: none }
-
-.ffz-bttv .emoticon.emoji { padding-top: 0 }
-
-/* Host Menu */
-
-.ffz-subwindow input {
- padding: 5px;
-}
-
-.ffz-subwindow p {
- margin-bottom: 0;
- padding: 0 0 10px;
-}
-
-#ffz-metadata-popup,
-.ffz-subwindow .card,
-.ffz-channel-selector {
- background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
- background-repeat: no-repeat;
- background-size: 50%;
- background-position: 110% 115%;
-}
-
-.ffz-channel-selector .dropmenu_action {
- display: block;
- position: relative;
- line-height: 34px;
- overflow: hidden;
- padding: 0 !important;
-}
-
-.ffz-channel-selector .image {
- display: inline-block;
- width: 20px;
- height: 20px;
- float: left;
- margin: 6px 15px 8px 20px;
-}
-
-.ffz-channel-selector .title {
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- display: block;
- padding-right: 20px;
-}
-
-.ffz-channel-selector .header {
- padding: 5px 20px;
- margin-bottom: 0;
- border-bottom: none;
- line-height: 22px;
- text-transform: uppercase;
- font-size: 11px;
- color: #666;
-}
-
-
-/* Oh my god this is awful why does Chat Replay move chat lines? */
-
-.app-main.theatre .chatReplay .chat-messages .chat-line .mod-icons,
-.ffz-dark .chatReplay .chat-messages .chat-line .mod-icons {
- background-color: rgba(0,0,0,0.8);
-}
-
-.chatReplay .chat-messages .chat-line .mod-icons {
- position: absolute;
- z-index: 10;
- left: 0; top: 0; bottom: 0;
- padding: 3px 5px 4px 4px;
- background: rgba(255,255,255,0.8);
- transition: margin-left .08s linear;
-}
-
-.chatReplay .show-mod-icons .chat-line .mod-icons { transition: none }
-.chatReplay .chat-messages .chat-line:hover { overflow: visible }
-
-.chatReplay .chat-messages .chat-line:hover .mod-icons {
- margin-left: 0;
-}
-
-.chatReplay .chat-messages .horizontal-line { margin: 4px 0 }
-
-
-/* Positioning Fixes */
-
-body:not(.ffz-bttv) .ember-chat .chat-settings { bottom: 40px; }
-body:not(.ffz-bttv) .notification-controls .notify-menu { bottom: 25px; }
-body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
-
-
-/* Dashboard Changes */
-
-#dash_main #stream-config-status, #chart_container.ffz-stat-chart { margin: 20px auto 0 }
-
-/*#dash_main #video_player { height: 303.75px }
-
-#stream-config-status { display: none }
-
-#delay_controls { float: left }
-#delay_controls + #commercial_buttons { float: right }
-#delay_controls + #commercial_buttons + div { clear: both }
-
-#delay_controls, #delay_controls + #commercial_buttons {
- width: 50%;
- margin-bottom: 20px;
-}
-
-#dash_main #delay_controls, #dash_main #commercial_buttons {
- margin-top: 0;
- padding-top: 0;
- border-top: none;
-}
-
-#dash_main #delay_controls .section_header, #commercial_buttons .section_header { padding-top: 0 }
-#dash_main #delay_controls .ui-slider { margin-top: 18px }*/
-
-
-/* Menu Scrollbar */
-
-.offer-list__core::-webkit-scrollbar,
-.notification-center__content::-webkit-scrollbar,
-.balloon__list.scroll-y::-webkit-scrollbar,
-.player-menu__menu::-webkit-scrollbar,
-.dash-column::-webkit-scrollbar,
-#ffz-metadata-popup .scroller::-webkit-scrollbar,
-.search-panel .collection-wrapper::-webkit-scrollbar,
-.activity-react__all::-webkit-scrollbar,
-.conversations-list .scroll-container::-webkit-scrollbar,
-.chatters-container::-webkit-scrollbar,
-.ffz-scrollbar::-webkit-scrollbar,
-.ember-chat .chat-settings::-webkit-scrollbar,
-.conversations-list .conversations-list-inner::-webkit-scrollbar,
-.conversation-window .conversation-content::-webkit-scrollbar,
-.chat-history::-webkit-scrollbar,
-#ffz-metadata-popup[data-key="srl_race"] .table::-webkit-scrollbar,
-.emoticon-selector-box .all-emotes::-webkit-scrollbar,
-.ffz-ui-sub-menu-page::-webkit-scrollbar,
-.ffz-ui-menu-page::-webkit-scrollbar {
- height: 6px;
- width: 6px;
-}
-
-.offer-list__core::-webkit-scrollbar-thumb,
-.notification-center__content::-webkit-scrollbar-thumb,
-.balloon__list.scroll-y::-webkit-scrollbar-thumb,
-.player-menu__menu::-webkit-scrollbar-thumb,
-.dash-column::-webkit-scrollbar-thumb,
-#ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
-.search-panel .collection-wrapper::-webkit-scrollbar-thumb,
-.activity-react__all::-webkit-scrollbar-thumb,
-.conversations-list .scroll-container::-webkit-scrollbar-thumb,
-.chatters-container::-webkit-scrollbar-thumb,
-.ffz-scrollbar::-webkit-scrollbar-thumb,
-.ember-chat .chat-settings::-webkit-scrollbar-thumb,
-.conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
-.conversation-window .conversation-content::-webkit-scrollbar-thumb,
-.chat-history::-webkit-scrollbar-thumb,
-#ffz-metadata-popup[data-key="srl_race"] .table::-webkit-scrollbar-thumb,
-.emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
-.ffz-ui-sub-menu-page::-webkit-scrollbar-thumb,
-.ffz-ui-menu-page::-webkit-scrollbar-thumb {
- border-radius: 7px;
- background: rgba(0,0,0,0.7);
- box-shadow: 0 0 1px 1px rgba(255,255,255,0.25);
-}
-
-.ffz-dark .offer-list__core::-webkit-scrollbar-thumb,
-.ffz-dark .notification-center__content::-webkit-scrollbar-thumb,
-.ffz-dark .balloon__list.scroll-y::-webkit-scrollbar-thumb,
-.ffz-dark .player-menu__menu::-webkit-scrollbar-thumb,
-
-.ffz-dark .dash-column::-webkit-scrollbar-thumb,
-.theatre .dash-column::-webkit-scrollbar-thumb,
-
-.ffz-dark #ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
-.theatre #ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
-
-.ffz-dark .search-panel .collection-wrapper::-webkit-scrollbar-thumb,
-.ffz-dark .activity-react__all::-webkit-scrollbar-thumb,
-.ffz-dark .conversations-list .scroll-container::-webkit-scrollbar-thumb,
-.ffz-dark .ffz-scrollbar::-webkit-scrollbar-thumb,
-.ffz-dark .table::-webkit-scrollbar-thumb,
-.ffz-dark .conversation-window .conversation-content::-webkit-scrollbar-thumb,
-.ffz-dark .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
-.ffz-dark .conversation-input-bar .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
-
-.theatre .offer-list__core::-webkit-scrollbar-thumb,
-.theatre .search-panel .collection-wrapper::-webkit-scrollbar-thumb,
-.theatre .conversations-list .scroll-container::-webkit-scrollbar-thumb,
-.theatre .chatters-container::-webkit-scrollbar-thumb,
-.theatre .ffz-scrollbar::-webkit-scrollbar-thumb,
-.theatre .ember-chat .chat-settings::-webkit-scrollbar-thumb,
-.theatre .conversation-window .conversation-content::-webkit-scrollbar-thumb,
-.theatre .conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
-
-.ffz-dark .chat-history::-webkit-scrollbar-thumb,
-.theatre .chat-history::-webkit-scrollbar-thumb,
-.theatre .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
-.theatre .ffz-ui-menu-page::-webkit-scrollbar-thumb,
-.theatre .ffz-ui-sub-menu-page::-webkit-scrollbar-thumb,
-
-.dark .offer-list__core::-webkit-scrollbar-thumb,
-.dark .chatters-container::-webkit-scrollbar-thumb,
-.dark .ffz-scrollbar::-webkit-scrollbar-thumb,
-.dark .ember-chat .chat-settings::-webkit-scrollbar-thumb,
-.dark .chat-history::-webkit-scrollbar-thumb,
-.dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
-.dark .ffz-ui-menu-page::-webkit-scrollbar-thumb,
-.dark .ffz-ui-sub-menu-page::-webkit-scrollbar-thumb,
-
-.theme--dark .offer-list__core::-webkit-scrollbar-thumb,
-.theme--dark .chatters-container::-webkit-scrollbar-thumb,
-.theme--dark .ffz-scrollbar::-webkit-scrollbar-thumb,
-.theme--dark .ember-chat .chat-settings::-webkit-scrollbar-thumb,
-.theme--dark .chat-history::-webkit-scrollbar-thumb,
-.theme--dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
-.theme--dark .ffz-ui-menu-page::-webkit-scrollbar-thumb,
-.theme--dark .ffz-ui-sub-menu-page::-webkit-scrollbar-thumb {
- background: rgba(255,255,255,0.6);
- box-shadow: 0 0 1px 1px rgba(0,0,0,0.25);
-}
-
-
-.chatters-container {
- overflow-x: hidden !important;
- overflow-y: auto !important;
-}
-
-/* Fix Moderation Cards */
-
-.ffz-moderation-card {
- border: 2px solid #cbcbcb;
- width: 340px;
- line-height: inherit !important;
-}
-
-.moderation-card__close-button {
- margin-right: 0 !important;
-}
-
-.ffz-moderation-card .extra-interface {
- padding-top: 0;
-}
-
-.ffz-moderation-card .extra-interface + .extra-interface {
- margin-top: -10px;
-}
-
-.ember-chat .chat-messages .chat-line--force-timestamp .timestamp,
-.ffz-moderation-card.ffz-has-info .moderation-card__name .intl-login { display: inline }
-
-.ffz-moderation-card.ffz-has-info .flex { position: relative }
-
-.ffz-moderation-card .flex .info {
- position: absolute;
- bottom: 0;
- left: 50px;
- height: 18px;
- line-height: 18px;
- z-index: 4;
- pointer-events: none;
- margin-left: 0 !important;
-}
-
-.ffz-moderation-card .info .stat {
- pointer-events: auto;
-}
-
-.ffz-moderation-card .info.channel-stats .stat {
- color: #fff !important;
- padding-left: 10px;
-}
-
-.ffz-moderation-card .info.channel-stats .stat svg {
- margin: 1px 5px 1px 0;
- pointer-events: none;
-}
-
-.ffz-moderation-card .info svg path { fill: #fff !important; }
-
-.ffz-moderation-card button {
- margin: 0;
- padding: 0 5px;
- color: #6441a4;
-}
-
-.ffz-moderation-card .button.ignore { margin-left: 0 !important }
-
-.ffz-moderation-card .moderation-card__controls button figure { padding: 0 }
-
-.ffz-moderation-card .follow-button,
-.ffz-moderation-card .friend-button { max-height: 30px }
-
-.ffz-moderation-card .right button:last-of-type,
-.ffz-moderation-card .mod-controls:last-of-type button:last-of-type { margin-right: 0 }
-
-body:not(.ffz-hide-friends) .ffz-moderation-card .follow-button {
- font-size: 0 !important;
- padding-right: 0 !important;
- transition: none !important;
-}
-
-.ffz-moderation-card .button.button--icon-only {
- padding: 0 2.5px 0 2.5px !important;
- margin-right: 5px;
-}
-
-.ffz-moderation-card .button.button--icon-only figure { padding: 0 !important }
-
-.ffz-moderation-card button:not(.button--icon-only):hover,
-.ffz-moderation-card button:not(.button--icon-only):focus {
- color: #fff;
- background-color: rgba(117,80,186, 1);
-}
-
-.ffz-moderation-card button.message {
- height: 30px;
- font-size: 1.2em;
-}
-
-.ffz-moderation-card.ffz-is-mod .button.ban,
-.ffz-moderation-card.ffz-is-mod .button.mod,
-.ffz-moderation-card.ffz-is-mod .button.unban,
-.ffz-moderation-card span.right {
- float: right;
-}
-
-.ffz-moderation-card:focus {
- outline: none;
- border-color: #444;
- /*box-shadow: #000 0 0 5px;*/
-}
-
-.ffz-moderation-card ul.menu + .moderation-card__actions { border-top: none }
-
-.ffz-moderation-card .moderation-card__actions:last-child,
-.ffz-moderation-card .moderation-card__actions:not(:last-of-type) {
- border-bottom: none;
-}
-
-.ffz-moderation-card .info,
-.ffz-moderation-card .moderation-card__name {
- text-shadow: black 0 0 5px;
-}
-
-.ffz-moderation-card .moderation-card__name {
- max-width: 235px;
-}
-
-.ffz-moderation-card .ffz-ban-reasons {
- margin-top: 10px;
- width: 100%;
-}
-
-.ffz-moderation-card ul.menu {
- list-style-type: none;
- padding: 0 !important;
- border: none !important;
- border-bottom: 1px solid rgba(0,0,0,0.2) !important;
-}
-
-.ffz-moderation-card ul.menu span { text-decoration: underline }
-
-.ffz-dark .ffz-schedule-row,
-.theatre .ffz-schedule-row,
-
-.ffz-dark .ffz-following-row,
-.theatre .ffz-following-row,
-
-.ffz-bttv-dark .moderation-card .moderation-card__actions,
-.ffz-dark .moderation-card .moderation-card__actions,
-.theatre .moderation-card .moderation-card__actions,
-.theme--dark .moderation-card .moderation-card__actions,
-.dark .moderation-card .moderation-card__actions,
-
-.ffz-bttv-dark .ffz-moderation-card ul.menu,
-.ffz-dark .ffz-moderation-card ul.menu,
-.theatre .ffz-moderation-card ul.menu,
-.dark .ffz-moderation-card ul.menu,
-.theme--dark .ffz-moderation-card ul.menu {
- border-bottom-color: rgba(255,255,255,0.2) !important;
-}
-
-.ffz-moderation-card ul.menu li:first-of-type { margin-left: 5px }
-.ffz-moderation-card ul.menu li:last-of-type { margin-right: 5px }
-
-.ffz-moderation-card ul.menu li {
- display: block;
- cursor: pointer;
- float: left;
- padding: 5px 5px 3px;
- margin-bottom: -1px;
- border-bottom: 2px solid transparent;
-}
-
-.ffz-moderation-card ul.menu li:hover {
- color: #7d5bbe;
- border-bottom-color: #7d5bbe;
-}
-
-.ffz-bttv-dark .ffz-moderation-card ul.menu li,
-.ffz-dark .ffz-moderation-card ul.menu li,
-.theatre .ffz-moderation-card ul.menu li,
-.dark .ffz-moderation-card ul.menu li,
-.theme--dark .ffz-moderation-card ul.menu li {
- color: #999;
-}
-
-.ffz-bttv-dark .ffz-moderation-card ul.menu li:hover,
-.ffz-dark .ffz-moderation-card ul.menu li:hover,
-.theatre .ffz-moderation-card ul.menu li:hover,
-.dark .ffz-moderation-card ul.menu li:hover,
-.theme--dark .ffz-moderation-card ul.menu li:hover {
- color: #a68ed2;
- border-bottom-color: #a68ed2;
-}
-
-.ffz-moderation-card ul.menu li.active { border-bottom-color: #6441a4 }
-
-.ffz-moderation-card:not(.ffz-default-tab) > .moderation-card__actions:not(.menu) {
- display: none
-}
-
-
-/* dark moderation card */
-
-.ffz-bttv-dark .ffz-moderation-card,
-.dark .ffz-moderation-card,
-.theme--dark .ffz-moderation-card,
-.theatre .ffz-moderation-card {
- border-color: #1b1b20;
-}
-
-.ffz-bttv-dark .ffz-moderation-card:focus,
-.dark .ffz-moderation-card:focus,
-.theme--dark .ffz-moderation-card:focus,
-.theatre .ffz-moderation-card:focus {
- border-color: #cbcbcb;
-}
-
-.ffz-bttv-dark .ffz-moderation-card .moderation-card__actions,
-.dark .ffz-moderation-card .moderation-card__actions,
-.theme--dark .ffz-moderation-card .moderation-card__actions,
-.theatre .ffz-moderation-card .moderation-card__actions {
- background-color: #232329;
-}
-
-.ffz-bttv-dark.ffz-no-blue .moderation-card .moderation-card__actions,
-.ffz-no-blue .cn-hosting,
-.ffz-no-blue .dark .ember-chat .moderation-card .moderation-card__actions,
-.ffz-no-blue .theme--dark .ember-chat .moderation-card .moderation-card__actions,
-.ffz-no-blue .theatre .ember-chat .moderation-card .moderation-card__actions {
- background-color: #232323;
-}
-
-.moderation-card .moderation-card__name a { color: #fff !important; }
-
-
-/* purge icon */
-
-.mod-icons .purge {
- background-image: url('//cdn.frankerfacez.com/script/PurgeButton.svg');
- background-repeat: no-repeat;
-}
-
-.mod-icons .tb-allow { background-image: url("//cdn.frankerfacez.com/script/button_accept.svg") }
-.mod-icons .tb-reject { background-image: url("//cdn.frankerfacez.com/script/button_reject.svg") }
-
-.mod-icons .pc-dismiss { background-image: url("//cdn.frankerfacez.com/script/button_trash.svg") }
-.mod-icons .pc-dismiss-local { background-image: url("//cdn.frankerfacez.com/script/button_close.svg") }
-
-.mod-icons .custom {
- text-indent: 0 !important;
- text-align: center;
- text-decoration: none;
- font-size: 18px;
- font-weight: bold;
- margin-top: -1px !important;
- color: #888 !important;
- background: none !important;
-}
-
-.mod-icons .mod-icon img {
- pointer-events: none;
-}
-
-
-/* Chat Rows */
-
-.conversation-window .conversation-chat-line {
- margin: 0;
- padding: 3px 10px;
-}
-
-.ember-chat .chat-messages .chat-line {
- margin: 0;
- padding: 3px 20px;
- position: relative;
-}
-
-.theatre .conversation-window .conversation-chat-line,
-.dark .chat-line,
-.theme--dark .chat-line,
-.theatre .chat-line {
- color: #acacbf;
-}
-
-.ffz-alias-italics .ffz-alias { font-style: italic; }
-
-.ember-chat .chat-messages .chat-line.ffz-has-deleted {
- line-height: 30px;
-}
-
-.mod-icon.inactive {
- opacity: 0.25;
- filter: grayscale(1);
-}
-
-.chat-line.ffz-deleted > span {
- opacity: 0.5;
-}
-
-.chat-line.ffz-deleted > span.message {
- text-decoration: line-through;
-}
-
-.chat-line.ffz-deleted:hover > span {
- opacity: 0.9;
-}
-
-.chat-line.ffz-deleted:hover > span.message {
- text-decoration: none;
-}
-
-body:not(.ffz-bttv) .chat-container:not(.chatReplay) .more-messages-indicator {
- /* This looks better when it's full width. */
- margin: 0 -20px;
-}
-
-.chat-line .message {
- word-break: break-word;
-}
-
-.ember-chat .chat-messages .tse-scroll-content {
- padding: 0;
-}
-
-
-/* Emoticon Tooltips */
-
-#ffz-price-popup hr,
-.ffz-subwindow hr {
- border-bottom-width: 1px;
- border-bottom-style: solid;
- margin: 10px 0;
-}
-
-.tipsy-inner hr {
- margin: 5px 0;
- border-bottom-width: 1px;
- border-bottom-style: solid;
-}
-
-.ffz-wide-tip .tipsy-inner {
- min-width: 300px;
- max-width: 600px;
- text-align: left;
- position: relative;
-}
-
-.tipsy-inner .faded {
- opacity: 0.7;
-}
-
-.ffz-follow-tip .tipsy-inner {
- width: 316px;
-}
-
-.ffz-wide-tip span.stat {
- float: right;
- margin-left: 5px;
-}
-
-.ffz-wide-tip a b { color: #fff }
-
-.ffz-wide-tip b { margin-right: 20px; }
-
-.ffz-wide-tip span.stat svg {
- float: left;
- margin: 1px;
-}
-
-.ffz-wide-tip svg path { fill: #fff; }
-.ffz-wide-tip span.playing {
- opacity: 0.7;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- display: block;
- position: relative;
- left: 0;
- right: 0;
-}
-
-.tipsy .tipsy-inner {
- white-space: pre-wrap;
-}
-
-
-.ffz-rich-tip .tipsy-inner {
- max-width: 340px;
- width: 340px;
- text-align: left;
- position: relative;
-
- padding: 8px;
-}
-
-.ffz-rich-tip .body { line-height: 1.5em }
-
-.ffz-rich-tip .tweet-heading:before {
- content: '';
- position: absolute;
- top: 24px;
- right: 24px;
- width: 20px;
- height: 16px;
- background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px;
-}
-
-.ffz-rich-tip .stats:after,
-.ffz-rich-tip .heading:after {
- content: '';
- display: table;
- clear: both;
-}
-
-.ffz-rich-tip .avatar {
- float: left;
- margin-right: 8px;
- max-height: 48px;
- max-width: 100px;
-}
-
-.ffz-rich-tip .tweet-heading .avatar {
- border-radius: 50%;
-}
-
-.ffz-rich-tip .heading .title {
- font-weight: bold;
- display: block;
- text-align: justify;
-}
-
-.ffz-rich-tip .display-name {
- padding-top: 3px;
- font-size: 14px;
- display: block;
-}
-
-.ffz-rich-tip .display-name.big-name {
- font-size: 20px;
- padding-top: 18px;
-}
-
-.ffz-rich-tip .quoted .display-name {
- padding: 0 5px 0 0;
- display: inline;
- font-size: 12px;
-}
-
-.ffz-rich-tip .twitch-heading .tip-badge.verified {
- height: 12px;
- width: 12px;
- margin: 2px 0 -1px 5px;
- background: url("https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1");
- background-size: contain;
- display: inline-block;
-}
-
-.ffz-rich-tip .twitch-heading .big-name .tip-badge.verified {
- height: 18px;
- width: 18px;
- margin: 1px 0 0 10px;
-}
-
-.ffz-rich-tip .tweet-heading .tip-badge {
- height: 12px;
- width: 12px;
- margin: 2px 0 -1px 5px;
- background: url("//cdn.frankerfacez.com/script/twitter_sprites.png");
- display: inline-block;
-}
-
-.ffz-rich-tip .tip-badge.verified {
- background-position: 0 -15px;
-}
-
-.ffz-rich-tip .tip-badge.translator {
- background-position: -12px -15px;
-}
-
-.ffz-rich-tip .tip-badge.protected {
- background-position: -24px -15px;
- width: 14px;
-}
-
-.ffz-rich-tip .emoji {
- height: 1em;
- vertical-align: middle;
-}
-
-.ffz-rich-tip .quoted > div:not(:first-child),
-.ffz-rich-tip .tipsy-inner > div:not(:first-child) {
- margin-top: 8px;
-}
-
-.ffz-rich-tip .quote-heading + .body,
-.ffz-rich-tip .replying + .body {
- margin-top: 0 !important;
-}
-
-.ffz-rich-tip .replying {
- opacity: 0.6;
- font-size: 10px;
-}
-
-.ffz-rich-tip .subtitle,
-.ffz-rich-tip .quoted .body,
-.ffz-rich-tip .stats,
-.ffz-rich-tip .username { opacity: 0.6 }
-
-.ffz-rich-tip .stats { display: flex }
-.ffz-rich-tip .stats .wide-stat,
-.ffz-rich-tip time { flex-grow: 1 }
-
-.ffz-rich-tip .tweet-stats .stat:before {
- content: '';
- display: inline-block;
- background: url("//cdn.frankerfacez.com/script/twitter_sprites.png");
- margin: 0 5px 0 10px;
-}
-
-.ffz-rich-tip .stat.likes:before {
- width: 17px; height: 14px;
- margin-bottom: -3px;
-}
-
-.ffz-rich-tip .stat.retweets:before {
- width: 20px; height: 12px;
- background-position: -34px 0;
- margin-bottom: -2px;
-}
-
-.ffz-rich-tip .media { position: relative; text-align: center }
-
-.ffz-rich-tip .media[data-count]:not([data-count="1"]) {
- display: flex;
- margin: 8px -5px -5px 0;
- flex-flow: column wrap;
- height: 329px;
-}
-
-.ffz-rich-tip .media .duration {
- position: absolute;
- bottom: 0; right: 0;
- margin: 4px;
- background: #000;
- opacity: .8;
- padding: 2px 4px;
- border-radius: 2px;
- z-index: 10;
- font-weight: 500;
-}
-
-.ffz-rich-tip .sixteen-nine {
- width: 100%;
- overflow: hidden;
- margin: 0;
- padding-top: 56.25%;
- position: relative;
-}
-
-.ffz-rich-tip .sixteen-nine img {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 100%;
- transform: translate(-50%, -50%);
-}
-
-.ffz-rich-tip .media video {
- width: 100%;
- max-height: 324px;
-}
-
-.ffz-rich-tip .media[data-count="4"] { flex-flow: row wrap }
-
-.ffz-rich-tip .media[data-count="2"] { height: 164.5px }
-.ffz-rich-tip .media img { max-height: 324px }
-
-.ffz-rich-tip .quoted .media[data-count]:not([data-count="1"]) { height: 150px }
-.ffz-rich-tip .quoted .media[data-count="2"] { height: 150px }
-.ffz-rich-tip .quoted .media video,
-.ffz-rich-tip .quoted .media img { max-height: 150px }
-
-.ffz-rich-tip .media span {
- background: no-repeat center center;
- background-size: cover;
-}
-
-.ffz-rich-tip .media[data-count="2"] span,
-.ffz-rich-tip .media[data-count="3"] span,
-.ffz-rich-tip .media[data-count="4"] span {
- width: calc(50% - 5px);
- height: calc(50% - 5px);
- margin: 0 5px 5px 0;
-}
-
-.ffz-rich-tip .media[data-count="2"] span,
-.ffz-rich-tip .media[data-count="3"] span:first-of-type {
- height: 100%;
-}
-
-.ffz-rich-tip .profile-stats {
- display: flex;
- flex-flow: row;
- flex-wrap: wrap;
-}
-
-.ffz-rich-tip .profile-stats div {
- flex-grow: 1;
- font-size: 16px;
- margin-right: 10px;
-}
-
-.ffz-rich-tip .profile-stats span {
- display: block;
- font-size: 12px;
- opacity: 0.7;
-}
-
-.ffz-rich-tip .quoted {
- border: 1px solid #474747;
- border-radius: 5px;
- padding: 8px;
-}
-
-.ffz-rich-tip .media[data-type="video"]:after {
- position: absolute;
- content: '';
- width: 64px; height: 64px;
- top: calc(50% - 32px);
- left: calc(50% - 32px);
- background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23FFF' d='M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z'/%3E%3C/svg%3E");
-}
-
-
-/* Menu Page Loader */
-
-.ffz-ui-sub-menu-page:empty,
-.ffz-ui-menu-page:empty {
- overflow: hidden;
-}
-
-.ffz-loading-spinner:after,
-.chat-history.loading:after,
-.ffz-async-tooltip:empty:after,
-.ffz-ui-sub-menu-page:empty::after,
-.ffz-ui-menu-page:empty::after {
- content: " ";
- display: block;
- width: 80px;
- height: 63px;
-
- background-image: url("//cdn.frankerfacez.com/script/spinner-dark.png");
-
- margin: 50px auto;
- animation: ffz-rotateplane 1.2s infinite linear;
-}
-
-.ffz-async-tooltip:empty:after {
- margin: 10px 50px;
-}
-
-@keyframes ffz-rotateplane {
- 0% { transform: perspective(120px) rotateY(90deg) }
- 25% { transform: perspective(120px) rotateY(180deg) }
- 75% { transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
- 100% { transform: perspective(120px) rotateY(90deg) rotateX(180deg) }
-}
-
-/* Menu About Page */
-
-#ffz-chat-menu .center { text-align: center }
-
-.ffz-about-table {
- width: 100%;
-}
-
-.ffz-about-table td:first-child {
- text-align: left;
- width: 100%;
-}
-
-.ffz-about-table .debug td {
- padding-top: 10px;
- opacity: 0.8;
- font-size: 10px;
-}
-
-.ffz-about-subheading {
- /*text-transform: uppercase;*/
- letter-spacing: 2px;
- margin: -5px 0 5px;
-}
-
-.button.ffz-news,
-.button.ffz-donate {
- margin-left: 10px;
- color: #fff !important;
- padding: 0 10px;
- font-size: 12px;
-}
-
-.button.ffz-issues { background: #750000; }
-.button.ffz-issues:not(.disabled):hover { background: #8a0303; }
-
-.button.ffz-ideas { background: #004e75; }
-.button.ffz-ideas:not(.disabled):hover { background: #035d8a; }
-
-.button.ffz-donate { background: #00b132; }
-.button.ffz-donate:not(.disabled):hover { background: #08c43d; }
-
-.button.ffz-news { background: #755000; }
-.button.ffz-news:not(.disabled):hover { background: #8a5f03; }
-
-
-/* Dumb Fixes */
-
-.channel-stats .stat { vertical-align: top; }
-
-.ffz-bttv .no-bttv { display: none; }
-
-.chat-container.dark,
-.theme--dark .chat-container,
-.theatre .chat-container {
- box-shadow: none;
-}
-
-
-/* Banned Words */
-
-.deleted-word {
- font-weight: bold;
- opacity: 0.35;
- cursor: pointer;
-}
-
-
-/* Unsafe Links */
-
-a.unsafe-link {
- color: #a64141 !important;
-}
-
-.theatre a.unsafe-link,
-.chat-container.dark a.unsafe-link,
-.theme--dark .chat-container a.unsafe-link {
- color: #d28e8e !important;
-}
-
-/* Chat Menu */
-
-.ffz-room-list > div.ffz + ul.room-list { display: block !important; }
-
-.ffz-room-list > div:not(.ffz),
-.ffz-room-list > ul:not(.ffz) {
- display: none !important;
-}
-
-.ffz-room-list > table {
- padding: 15px 0 0;
-}
-
-.ffz-room-list > table + table {
- margin-top: 10px;
-}
-
-.ffz-room-list > table th {
- padding: 2px 5px;
- color: #8c8c8c;
- font-weight: normal;
- text-transform: uppercase;
-}
-
-.ffz-room-list > table > tbody tr {
- line-height: 26px;
-}
-
-.ffz-room-list > table td {
- padding: 2px 0;
- text-align: center;
-}
-
-.ffz-room-list > table th:first-child,
-.ffz-room-list > table td:nth-child(0n+2) {
- width: 100%;
- text-align: left;
-}
-
-.ffz-room-row {
- cursor: pointer;
-}
-
-.ffz-room-list > table th:first-child,
-.ffz-room-list > table td:first-child {
- padding-left: 18px;
-}
-
-.ffz-room-list > table th:last-child,
-.ffz-room-list > table td:last-child {
- padding-right: 18px;
-}
-
-.ffz-room-list td svg {
- margin: 5px 10px 5px 0;
- float: left;
-}
-
-.ffz-bttv-dark .ffz-room-row,
-.ffz-dark .ffz-room-row { color: #a68ed2; }
-
-.ffz-bttv-dark .ffz-room-row svg path,
-.ffz-dark .ffz-room-row svg path { fill: #a68ed2; }
-
-.ffz-room-row { color: #6441a5; }
-.ffz-room-row svg path { fill: #6441a5; }
-
-.ffz-room-row:hover svg path,
-.ffz-room-row:focus svg path,
-.ffz-room-row.active svg path { fill: #fff; }
-
-.ffz-room-row:hover,
-.ffz-room-row:focus,
-.ffz-room-row.active {
- background-color: #6441A5;
- color: #fff !important;
-}
-
-th.ffz-row-switch {
- min-width: 40px;
-}
-
-.ffz-room-row a.leave-chat {
- float: right;
- padding: 0 7px;
-}
-
-.ffz-row-switch .switch {
- float: none;
- margin: 5px 0 -4px;
-}
-
-.ffz-row-switch .switch.active {
- background-color: #362359;
-}
-
-
-/* Chat Tabs */
-
-.ember-chat .chat-header,
-.ember-chat .chat-room { z-index: 5; }
-
-#ffz-group-tabs {
- padding: 10px 10px 6px;
- box-shadow: inset 0 -1px 0 0 rgba(0,0,0,0.2);
- display: flex;
- flex-wrap: wrap;
-}
-
-.ffz-chat-tab {
- flex-grow: 1;
- position: relative;
-
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- min-width: 70px;
-
- cursor: pointer;
- padding: 5px;
- margin: 0 4px 4px 0;
-
- display: inline-block;
-
- background-color: rgba(127,127,127,0.1);
- color: #6441A5;
-}
-
-
-.ffz-chat-tab svg path {
- fill: #6441A5;
-}
-
-#ffz-group-tabs .button {
- height: 30px;
- padding-bottom: 10px;
- margin-bottom: -10px;
- margin-right: 4px;
-}
-
-#ffz-group-tabs .button.button--icon-only svg {
- margin: 6px 0;
-}
-
-.ffz-chat-tab svg {
- width: 18px;
- height: 18px;
- margin: -5px 5px -5px 0;
-}
-
-.ffz-chat-tab:hover,
-.ffz-chat-tab:focus,
-.ffz-chat-tab.active {
- background-color: #6441A5;
- color: #fff !important;
-}
-
-.ffz-chat-tab.tab-mentioned {
- background-color: rgba(128,50,50,0.1);
- color: red !important;
-}
-
-.ffz-chat-tab.tab-mentioned:not(.active):hover,
-.ffz-chat-tab.tab-mentioned:not(.active):focus {
- background-color: #a54141;
- color: #fff !important;
-}
-
-.ffz-chat-tab:not(.active):hover,
-.ffz-chat-tab:not(.active):focus {
- background-color: #7550ba;
-}
-
-.ffz-chat-tab:hover svg path,
-.ffz-chat-tab:focus svg path,
-.ffz-chat-tab.active svg path {
- fill: #fff !important;
-}
-
-.ffz-chat-tab.active {
- cursor: default;
-}
-
-#ffz-group-tabs .button .notifications:empty,
-.ffz-room-row td > span:empty,
-.ffz-chat-tab span:empty { display: none; }
-
-.ffz-room-row td > span:not(.intl-login),
-.ffz-chat-tab span {
- padding: 0 4px;
- display: inline-block;
- border-radius: 2px;
- text-align: center;
- background-color: #f2f2f2;
- color: #666;
- position: absolute;
- right: 5px;
-}
-
-.ffz-room-row td {
- position: relative;
-}
-
-.ffz-room-row td > span:not(.intl-login) {
- line-height: 16px;
- margin: 5px 0;
-}
-
-.ffz-room-row.row-mentioned {
- background-color: rgba(128,50,50,0.1);
- color: red !important;
-}
-
-.ffz-room-row.row-mentioned:not(.active):hover,
-.ffz-room-row.row-mentioned:not(.active):focus {
- background-color: #a54141;
- color: #fff !important;
-}
-
-#ffz-group-tabs .button .notifications {
- background-color: #d44949;
- top: 0;
- right: 0;
- position: absolute;
- height: 12px;
- line-height: 12px;
- padding: 0 3px;
- width: auto;
- color: #fff;
- font-size: 9px;
- vertical-align: middle;
- margin: 0;
-}
-
-/* Dark Group Tabs */
-
-.ffz-bttv-dark #ffz-group-tabs,
-.app-main.theatre #ffz-group-tabs,
-.chat-container.dark #ffz-group-tabs,
-.theme--dark .chat-container #ffz-group-tabs {
- box-shadow: inset 0 -1px 0 0 #32323e;
-}
-
-.ffz-bttv-dark .ffz-chat-tab,
-.app-main.theatre .ffz-chat-tab,
-.chat-container.dark .ffz-chat-tab,
-.theme--dark .chat-container .ffz-chat-tab {
- color: #B9A3E3;
-}
-
-.ffz-bttv-dark .ffz-room-row td > span:not(.intl-login),
-.ffz-dark .ffz-room-row td > span:not(.intl-login),
-
-.ffz-bttv-dark .ffz-chat-tab span,
-.app-main.theatre .ffz-chat-tab span,
-.chat-container.dark .ffz-chat-tab span,
-.theme--dark .chat-container .ffz-chat-tab span {
- background-color: #19191f;
- color: #fff;
-}
-
-.ffz-bttv-dark .ffz-chat-tab svg path,
-.app-main.theatre .ffz-chat-tab svg path,
-.chat-container.dark .ffz-chat-tab svg path,
-.theme--dark .chat-container .ffz-chat-tab svg path {
- fill: #B9A3E3;
-}
-
-/* Minimalistic Chat */
-
-body.ffz-minimal-chat-head .ember-chat .chat-header,
-body.ffz-minimal-chat-head .ember-chat #ffz-group-tabs,
-body.ffz-minimal-chat-input .ember-chat .chat-buttons-container {
- display: none !important;
-}
-
-body.ffz-minimal-chat-head .chatters .chat-header { display: block !important }
-
-/*body.ffz-minimal-chat .ember-chat .chat-messages,
-body.ffz-minimal-chat .ember-chat .chat-interface .emoticon-selector {
- bottom: 33px;
-}*/
-
-body.ffz-minimal-chat-input .ember-chat .chat-interface .emoticon-selector {
- right: 10px;
-}
-
-body.ffz-minimal-chat-input .ember-chat .chat-interface .emoticon-selector .dropmenu {
- margin-bottom: 10px;
-}
-
-body.ffz-minimal-chat-head .ember-chat .chat-room {
- top: 0 !important;
-}
-
-body.ffz-minimal-chat-input .ember-chat .chat-interface {
- /*height: 33px !important;*/
- padding: 0;
-}
-
-body.ffz-minimal-chat-input .ember-chat .chat-messages .chat-lines { padding-bottom: 0 }
-
-body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain {
- top: 0 !important;
- margin: 0 !important;
- height: auto;
-}
-
-body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textarea {
- /*height: 33px !important;*/
- overflow: hidden;
- border-bottom: 0 !important;
- border-left: 0;
- border-right: 0;
-}
-
-/* Chat Pause */
-
-.ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator {
- z-index: 10;
- opacity: 1;
- cursor: default;
- pointer-events: none;
-}
-
-.chat-container:not(.chatReplay) .chat-interface .more-messages-indicator.ffz-freeze-indicator {
- top: 0;
-}
-
-/* Chat History */
-
-.ember-chat .moderation-card .chat-history,
-.chat-history {
- list-style-type: none;
- padding: 0;
- max-height: 320px;
- overflow-y: auto;
-}
-
-.chat-messages .tse-scroll-content,
-.chat-history {
- will-change: scroll-position, contents;
-}
-
-.moderation-card .chat-history.live-history {
- max-height: 200px;
-}
-
-.cp-hidden {
- display: inline-block;
- width: 1px; height: 1px;
- margin-left: -1px;
- overflow: hidden;
-}
-
-.chat-history.moderation-card__actions li:first-child { padding-top: 10px; }
-.chat-history.moderation-card__actions li:last-child { padding-bottom: 10px; }
-
-.chat-history .chat-line {
- overflow: hidden;
- line-height: 20px;
- padding: 4px 10px;
- word-wrap: break-word;
- list-style-position: unset;
-}
-
-.chat-history .timestamp-line {
- text-align: center;
- color: #8c8c8c;
- font-size: 1.1rem;
-}
-
-.ember-chat .moderation-card .moderation-card__actions.chat-history .chat-line.action {
- float: none;
- margin-right: 0px;
-}
-
-.chat-line.notification {
- border-bottom: none;
- background-color: transparent;
-}
-
-.chat-history .chat-line.notification .from,
-.chat-history .chat-line.notification .colon,
-.chat-history .chat-line.admin .from,
-.chat-history .chat-line.admin .colon { display: none }
-
-.chat-line .colon, .conversation-chat-line .colon { margin-left: 0 !important }
-
-.chat-history .chat-line.notification .message,
-.chat-history .chat-line.admin .message { color: #666; }
-
-.chat-history .timestamp {
- color: #8c8c8c;
- margin-right: 5px;
-}
-
-.chat-history .chat-line:not(.original-sender) .from:hover,
-.chat-history .timestamp.ts-action:hover {
- cursor: pointer;
- text-decoration: underline;
-}
-
-.chat-history .chat-line.admin.original-msg .message { color: #000 }
-
-.ffz-dark .chat-history .chat-line.admin.original-msg .message,
-.theatre .chat-history .chat-line.admin.original-msg .message,
-.dark .chat-history .chat-line.admin.original-msg .message,
-.theme--dark .chat-history .chat-line.admin.original-msg .message { color: #fff }
-
-
-.chat-history.loading {
- position: relative;
- min-height: 160px;
- overflow-y: hidden !important;
-}
-.chat-history.loading li { pointer-events: none; }
-
-.chat-history .from { font-weight: 700; }
-
-.chat-history.loading:before {
- content: " ";
- display: block;
- position: absolute;
- z-index: 999;
- top: 0; bottom: 0;
- left: 0; right: 0;
- background-color: rgba(128,128,128,0.8);
-}
-
-.theatre .chat-history.loading:before,
-.dark .chat-history.loading:before,
-.theme--dark .chat-history.loading:before {
- background-color: rgba(0,0,0,0.8);
-}
-
-.chat-history.loading:after {
- visibility: visible;
- clear: none;
- position: absolute;
- top: 0;
- left: 50%;
- margin-left: -40px;
- z-index: 1000;
-}
-
-.moderation-card .ffz-back-button,
-.moderation-card .ffz-load-more {
- display: block;
- text-align: center;
- padding: 0 1rem !important;
- margin: 10px;
-}
-
-.ffz-moderation-card .chat-back-button {
- border-bottom: 1px solid rgba(0,0,0,0.2) !important;
-}
-
-.ffz-chevron {
- position: relative;
- display: inline-block;
- height: 1rem;
- width: 1rem;
- margin: 0 .5rem;
-}
-
-.ffz-chevron:before {
- position: absolute;
- display: block;
- content: '';
- border: .5rem solid transparent;
-}
-
-.ffz-back-button .ffz-chevron {
- margin-left: -1rem;
-}
-
-.ffz-back-button .ffz-chevron:before {
- border-right-color: #fff;
-}
-
-.ffz-load-more:not(.load-after) .ffz-chevron:before {
- top: -.25rem;
- border-bottom-color: #fff;
-}
-.ffz-load-more.load-after .ffz-chevron:before {
- top: .25rem;
- border-top-color: #fff;
-}
-
-.ember-chat .moderation-card .back-button {
- border: 1px solid rgba(0,0,0,0.2);
- border-top: none;
- float: none;
- display: block;
- width: 100%;
- background-color: #fff;
-}
-
-.ember-chat .moderation-card .back-button:hover {
- background-color: #6441A5 !important;
- color: #fff !important;
-}
-
-.ffz-bttv-dark .moderation-card .back-button,
-.theatre .moderation-card .back-button,
-.dark .moderation-card .back-button,
-.theme--dark .moderation-card .back-button {
- background-color: #232329;
-}
-
-
-/* Room State */
-
-.ffz.room-state.stat {
- line-height: 30px;
- margin-left: -10px;
- margin-right: 15px;
- cursor: default;
-}
-
-.ffz.room-state.faded { opacity: 0.5 }
-
-.ffz.room-state.truncated span { font-size: 8px; }
-
-.button.primary.ffz-waiting:not(:hover) {
- background-color: rgba(0,0,0,0.1);
- color: #32323e;
-}
-
-.button.primary.ffz-waiting.ffz-banned:not(:hover) {
- background-color: rgba(128,0,0,0.1);
- color: #88323e;
-}
-
-.app-main.theatre .button-primary.ffz-waiting:not(:hover),
-.chat-container.dark .button.primary.ffz-waiting:not(:hover),
-.theme--dark .chat-container .button.primary.ffz-waiting:not(:hover) {
- background-color: rgba(255,255,255,0.1);
- color: #fff;
-}
-
-.app-main.theatre .button-primary.ffz-waiting.ffz-banned:not(:hover),
-.chat-container.dark .button.primary.ffz-waiting.ffz-banned:not(:hover),
-.theme--dark .chat-container .button.primary.ffz-waiting.ffz-banned:not(:hover) {
- background-color: rgba(255,128,128,0.1);
- color: #f66;
-}
-
-/* Swap Sidebars */
-
-.ffz-sidebar-swap .notification-center-balloon {
- left: auto !important;
- right: 0 !important;
-}
-
-body[data-current-path^="user."].ffz-portrait #right_close { transform: rotate(90deg); }
-body[data-current-path^="user."].ffz-portrait .archives-contain .more-archives { width: 100%; }
-
-body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-interface .emoticon-selector,
-.ffz-sidebar-swap:not(.ffz-portrait) .ember-chat .chat-interface .emoticon-selector {
- right: auto;
- left: 20px;
-}
-
-.ffz-sidebar-swap .search-panel--slide {
- left: auto;
- right: 240px;
-}
-
-.ffz-sidebar-swap .cn-bar-fixed {
- right: 50px;
-}
-
-.ffz-sidebar-swap:not(.ffz-sidebar-minimize) #left_col.open ~ #main_col .cn-bar-fixed {
- right: 240px;
-}
-
-.ffz-sidebar-swap .social-column,
-.ffz-sidebar-swap #left_col {
- left: auto;
- right: 0;
-}
-
-.ffz-sidebar-swap #right_col {
- right: auto;
- left: 0;
-}
-
-.ffz-sidebar-swap:not(.ffz-portrait) #right_close,
-.ffz-sidebar-swap #left_close {
- transform: scaleX(-1);
-}
-
-.ffz-sidebar-swap #right_close {
- right: auto;
- left: 5px;
-}
-
-.ffz-sidebar-swap #left_close {
- right: 5px;
- left: auto;
-}
-
-.ffz-sidebar-swap .social-column {
- box-shadow: none !important
-}
-
-.ffz-hide-socialbar .has-sc .cn-bar-fixed { left: 0 !important }
-.ffz-sidebar-swap.ffz-hide-socialbar .has-sc .cn-bar-fixed { right: 0 }
-
-.ffz-hide-socialbar:not(.ffz-sidebar-swap) .has-sc #main_col {
- margin-left: 0 !important;
-}
-
-.ffz-sidebar-swap #main_col {
- margin-left: 340px !important;
- margin-right: 240px !important;
-}
-
-.ffz-sidebar-swap.ffz-hide-socialbar .has-sc #main_col.expandLeft,
-.ffz-sidebar-swap.ffz-hide-socialbar .has-sc #main_col {
- margin-right: 0 !important;
-}
-
-.ffz-sidebar-swap .app-main.theatre #main_col {
- margin-left: 340px !important;
- margin-right: 0px !important;
-}
-
-.ffz-sidebar-swap .app-main.theatre #main_col.expandRight {
- margin-left: 0px !important;
-}
-
-
-.ffz-sidebar-swap .exit-theatre {
- left: 30px;
-}
-
-.ffz-sidebar-swap #main_col.expandLeft {
- margin-right: 50px !important;
-}
-
-.ffz-sidebar-swap .has-sc:not(.has-sc--collapsed) #main_col.expandLeft {
- margin-right: 240px !important;
-}
-
-.ffz-sidebar-swap #main_col.expandRight {
- margin-left: 0px !important;
-}
-
-.ffz-sidebar-swap #flyout {
- left: auto !important;
- right: 50px;
-}
-
-.ffz-sidebar-swap #flyout .content {
- right: 10px;
-}
-
-.ffz-sidebar-swap #flyout .point {
- left: auto;
- right: -1px;
-}
-
-.ffz-sidebar-swap #flyout .point:before {
- border-left-color: #fff;
- border-right-color: transparent;
- right: auto;
- left: 0px;
-}
-
-.ffz-sidebar-swap #flyout .point:after {
- border-right-color: transparent;
- border-left-color: rgba(0,0,0,0.25);
- right: auto;
- left: 1px;
-}
-
-.ffz-dark.ffz-sidebar-swap #flyout .point:after {
- border-left-color: #32323e;
- border-right-color: transparent;
-}
-
-.ffz-dark.ffz-sidebar-swap #flyout .point:before {
- border-left-color: #101010;
- border-right-color: transparent;
-}
-
-/* No Blue */
-
-.ffz-dark .toast,
-.ffz-no-blue .dark .toast,
-.ffz-no-blue .theme--dark .toast {
- box-shadow: 0 0.2rem 0.7rem rgba(82,82,82,.2);
- border-color: #525252;
- background-color: #323232;
-}
-
-.ffz-dark .toast:hover,
-.ffz-no-blue .dark .toast:hover,
-.ffz-no-blue .theme--dark .toast:hover {
- box-shadow: 0 0.2rem 0.7rem rgba(127,127,127,.4);
-}
-
-.ffz-dark .toast__content-msg { color: #EEE }
-.ffz-dark .toast__details { color: #999 }
-
-.ffz-dark .toast__action-wrapper,
-.ffz-dark .toast__action--primary,
-.ffz-no-blue .theme--dark .toast__action-wrapper,
-.ffz-no-blue .theme--dark .toast__action--primary {
- border-color: #525252;
-}
-
-.ffz-dark .toast__action--primary { color: #c5b6e2 }
-.ffz-dark .toast__action--secondary { color: #aaa }
-
-.ffz-dark .toast__action--secondary:hover,
-.ffz-dark .toast__action--primary:hover {
- background-color: #525252;
- color: #eee;
-}
-
-
-.ffz-no-blue .theme--dark .sc-collapsed-header,
-.ffz-no-blue .theme--dark .sc-header { box-shadow: 0 -0.1rem 0 #5c5c5c inset }
-.ffz-no-blue .theme--dark .js-sc-card:hover { box-shadow: -0.1rem 0 0 #414141 inset }
-.ffz-no-blue .theme--dark .social-column { box-shadow: -0.1rem 0 0 #2b2b2b inset}
-
-.ffz-no-blue .theme--dark .form__input,
-.ffz-no-blue .form--theme-dark .form__input {
- background-color: rgba(255,255,255,0.05);
- box-shadow: rgba(255,255,255,0.1) 0 0 0 1px inset;
-}
-
-.ffz-no-blue .theme--dark .form__input:focus,
-.ffz-no-blue .form--theme-dark .form__input:focus {
- box-shadow: rgba(255,255,255,0.3) 0 0 0 1px inset;
-}
-
-.ffz-no-blue .theatre .ember-chat .chat-interface .textarea-contain textarea {
- background-color: rgba(255,255,255,0.05);
- border-color: rgba(255,255,255,0.1);
-}
-
-.ffz-no-blue #carousel .nav {
- background-color: rgba(25,25,25,.5);
-}
-
-.ffz-no-blue .theme--dark .js-sc-card:hover,
-.ffz-no-blue .theme--dark .sc-toggle:hover,
-.ffz-no-blue .warp__item a:not(.warp__logo):hover {
- background-color: #2b2b2b;
-}
-
-.ffz-no-blue .warp__item--toggled > a:not(.warp__logo) {
- background-color: #2b2b2b;
- box-shadow: 3px 0 0 #a3a3a3 inset;
-}
-
-.ffz-no-blue .theme--dark .social-column,
-.ffz-no-blue .theatre .conversations-list-bottom-bar,
-.ffz-no-blue.ffz-dark .conversations-list-bottom-bar,
-.ffz-no-blue .theatre .conversations-list,
-.ffz-no-blue.ffz-dark .conversations-list,
-.ffz-no-blue .theatre .conversations-list-header,
-.ffz-no-blue .theatre .conversation-window,
-.ffz-no-blue.ffz-dark .conversation-window,
-.ffz-no-blue .theatre .conversation-system-message,
-.ffz-no-blue.ffz-dark .conversation-system-message,
-
-.ffz-no-blue .theatre .bits-card,
-.ffz-no-blue .theme--dark .bits-card,
-.ffz-no-blue .dark .bits-card,
-
-.ffz-no-blue .theatre .bits-card--standard,
-.ffz-no-blue .dark .bits-card--standard,
-.ffz-no-blue .theme--dark .bits-card--standard,
-
-.ffz-no-blue .theme--dark .chat-container .chat-header,
-.ffz-no-blue .chat-container.dark .chat-header,
-.ffz-no-blue .theatre .chat-header,
-
-.ffz-no-blue .theme--dark .sc-search,
-.ffz-no-blue .warp,
-.ffz-no-blue #large_nav .content,
-.ffz-no-blue #small_nav .content,
-
-.ffz-no-blue .app-main.theatre .chat-container,
-.ffz-no-blue .theme--dark .chat-container,
-.ffz-no-blue .chat-container.dark,
-
-.ffz-no-blue .app-main.theatre .chat-container .chat-hidden-overlay,
-.ffz-no-blue .theme--dark .chat-container .chat-hidden-overlay,
-.ffz-no-blue .chat-container.dark .chat-hidden-overlay,
-
-.ffz-no-blue .app-main.theatre .chat-container .chatters-view,
-.ffz-no-blue .theme--dark .chat-container .chatters-view,
-.ffz-no-blue .chat-container.dark .chatters-view,
-
-.ffz-no-blue .app-main.theatre .chat-container .emoticon-selector .emoticon-selector-box,
-.ffz-no-blue .theme--dark .chat-container .emoticon-selector .emoticon-selector-box,
-.ffz-no-blue .chat-container.dark .emoticon-selector .emoticon-selector-box,
-
-.ffz-no-blue .app-main.theatre .chat-container .emoticon-selector .emoticon-grid,
-.ffz-no-blue .theme--dark .chat-container .emoticon-selector .emoticon-grid,
-.ffz-no-blue .chat-container.dark .emoticon-selector .emoticon-grid,
-
-.ffz-no-blue .app-main.theatre .chat-container .chat-commands-dropdown,
-.ffz-no-blue .theme--dark .chat-container .chat-commands-dropdown,
-.ffz-no-blue .chat-container.dark .chat-commands-dropdown,
-
-.ffz-no-blue .app-main.theatre .chat-container .chat-commands-dropdown li,
-.ffz-no-blue .chat-container.dark .chat-commands-dropdown li,
-
-.ffz-no-blue.error_500,
-.ffz-no-blue.error_400,
-.ffz-no-blue .takeover #carousel,
-.ffz-no-blue #carousel_and_background,
-.ffz-no-blue #carousel .items .pic img,
-.ffz-no-blue .app-main.theatre .archives-contain,
-.ffz-no-blue.ffz-dark .archives-contain,
-.ffz-no-blue #content .turbo_landing {
- background-color: #191919;
-}
-
-.ffz-no-blue .theme--dark .chat-chip,
-
-.ffz-no-blue .app-main.theatre .bits-footer,
-.ffz-no-blue .theme--dark .bits-footer,
-.ffz-no-blue .dark .bits-footer,
-
-.ffz-no-blue .theatre .conversation-input-bar textarea,
-.ffz-no-blue .theatre input.text,
-.ffz-no-blue .theatre input.countries-input,
-.ffz-no-blue .theatre .recurly input,
-.ffz-no-blue .recurly .theatre input {
- background-color: #202020;
-}
-
-.ffz-no-blue .warp__anchor,
-.ffz-no-blue .warp__item--anchor,
-.ffz-no-blue .warp__item--anchor-beta,
-.ffz-no-blue .warp__drawer,
-.ffz-no-blue .leaf,
-
-.ffz-no-blue .theme--dark .emoticon-selector .tabs,
-.ffz-no-blue .dark .emoticon-selector .tabs,
-.ffz-no-blue .theatre .emoticon-selector .tabs,
-
-.ffz-no-blue .app-main.theatre .archives-contain .list-video:hover,
-.ffz-no-blue.ffz-dark .archives-contain .list-video:hover,
-
-.ffz-no-blue .theatre .moderation-card .back-button,
-.ffz-no-blue .theme--dark .moderation-card .back-button,
-.ffz-no-blue .dark .moderation-card .back-button,
-
-.ffz-no-blue .theme--dark .chat-container .chat-interface .emoticon-selector .tabs,
-.ffz-no-blue .chat-container.dark .chat-interface .emoticon-selector .tabs,
-.ffz-no-blue .app-main.theatre .chat-container .chat-interface .emoticon-selector .tabs {
- background-color: #232323;
-}
-
-.ffz-no-blue .theatre .conversations-list-bottom-bar,
-.ffz-no-blue.ffz-dark .conversations-list-bottom-bar,
-.ffz-no-blue .theatre .conversations-list .conversation-preview-line,
-.ffz-no-blue.ffz-dark .conversations-list .conversation-preview-line,
-.ffz-no-blue .theatre .conversation-window,
-.ffz-no-blue.ffz-dark .conversation-window {
- color: #8c8c8c;
-}
-
-.ffz-no-blue .theme--dark .sc-search,
-.ffz-no-blue .theatre .conversations-list,
-.ffz-no-blue.ffz-dark .conversations-list {
- border-color: #323232;
-}
-
-.ffz-no-blue .theatre .converations-list:before,
-.ffz-no-blue.ffz-dark .conversations-list:before {
- border-color: transparent;
- border-top-color: #323232;
-}
-
-.ffz-no-blue .theatre .conversations-list:after,
-.ffz-no-blue.ffz-dark .conversations-list:after {
- border-color: transparent;
- border-top-color: #191919;
-}
-
-.ffz-no-blue .theatre .conversations-list .conversations-list-header,
-.ffz-no-blue.ffz-dark .conversations-list .conversations-list-header,
-.ffz-no-blue .theatre .conversations-list .conversations-list-item,
-.ffz-no-blue.ffz-dark .conversations-list .conversations-list-item {
- border-bottom-color: #323232;
-}
-
-.ffz-no-blue .theatre .convoHeader,
-.ffz-no-blue.ffz-dark .convoHeader,
-.ffz-no-blue .theatre .conversations-list .search-divider,
-.ffz-no-blue.ffz-dark .conversations-list .search-divider,
-.ffz-no-blue.ffz-dark .conversations-list .conversations-list-header,
-.ffz-no-blue .theatre .conversation-window.has-focus .convoHeader,
-.ffz-no-blue.ffz-dark .conversation-window.has-focus .convoHeader,
-.ffz-no-blue .theatre .conversations-list .conversations-list-item:hover,
-.ffz-no-blue.ffz-dark .conversations-list .conversations-list-item:hover {
- background-color: #121212;
-}
-
-.ffz-no-blue .top-nav-drawer__status {
- background-color: #080808;
-}
-
-.ffz-no-blue .theme--dark .chat-commerce-rich-content__drawer,
-.ffz-no-blue .top-nav-drawer {
- background-color: #101010;
-}
-
-.ffz-no-blue .theme--dark .chat-commerce-right-content__pocket--button:hover {
- background-color: #171717;
-}
-
-.theme--dark .chat-chip {
- box-shadow: 0 1px 2px 0 rgba(0,0,0,.85);
-}
-
-.ffz-rich-content p.card__info {
- line-height: 1.4rem;
-}
-
-/* Following Count */
-
-li[data-name="following"] a {
- position: relative;
-}
-
-.ffz-follow-count:empty { display: none; }
-
-.ffz-follow-count {
- display: inline-block;
- border-radius: 2px;
- text-align: center;
- color: #fff;
-}
-
-#header_following .ffz-follow-count {
- margin: 0 5px;
- padding: 0 5px;
- line-height: 20px;
- background-color: rgba(25,25,25,0.5);
-}
-
-#left_col.open .warp__item .ffz-follow-count,
-#large_nav .ffz-follow-count,
-.ffz-dark #header_following .ffz-follow-count {
- background-color: rgba(127,127,127,0.5);
-}
-
-#left_col.open .warp__item .ffz-follow-count,
-#large_nav .ffz-follow-count {
- position: absolute;
- right: 10px;
- top: 8px;
-
- line-height: 14px;
- padding: 2px 5px;
-}
-
-#left_col.open .warp__item .ffz-follow-count {
- top: 6px;
- right: 20px;
-}
-
-#left_col.closed .warp__item .ffz-follow-count,
-#small_nav .ffz-follow-count {
- position: absolute;
- bottom: 2px;
- right: 5px;
- padding: 2px;
- font-size: 10px;
- line-height: 10px;
- background-color: #191919;
- color: rgba(255,255,255,0.5);
-}
-
-#small_nav .game_filter.selected a .ffz-follow-count,
-#small_nav .content ul li a:hover .ffz-follow-count {
- background-color: #101014;
-}
-
-/* Image Tooltips */
-
-.ffz-clip-title {
- display: block;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: 5px;
-}
-
-.ffz-tt-channel-title {
- display: block;
-}
-
-.ffz-image-hover {
- border:none;
- max-width: 186px;
- max-height: 186px;
- overflow: hidden;
-}
-
-.emoticon.ffz-image-hover {
- display: block;
- margin: 5px auto;
-}
-
-.ffz-follow-tip .ffz-image-hover {
- margin-top: 3px;
- max-width: 300px;
- max-height: 300px;
-}
-
-.ffz-yt-thumb {
- max-height: 90px;
-}
-
-/* Player Captions */
-
-.ffz-dark .player-modal__content,
-.theatre .player-modal__content {
- background: #101010;
- color: #ccc;
- box-shadow: 0 1px 1px rgba(255,255,255,.1);
-}
-
-.ffz-dark .player-modal__header,
-.theatre .player-modal__header {
- color: #ccc;
-}
-
-.ffz-dark .player-modal__content label,
-.theatre .player-modal__content label {
- color: #aaa;
-}
-
-.ffz-dark .cc-font-size,
-.theatre .cc-font-size {
- background-color: #101010;
- border-color: #333;
-}
-
-
-
-.ffz-dark .cc-color-palette__square:not(:hover),
-.theatre .cc-color-palette__square:not(:hover),
-
-.ffz-dark .cc-edge-palette__square:not(:hover),
-.theatre .cc-edge-palette__square:not(:hover),
-
-.ffz-dark .cc-preset-square:not(:hover):not(.js-cc-preset-selected),
-.theatre .cc-preset-square:not(:hover):not(.js-cc-preset-selected) {
- border-color: #101010;
-}
-
-.ffz-dark label.cc-edge-palette__square,
-.theatre label.cc-edge-palette__square { color: #fff }
-
-
-/* Classic Player */
-
-.ffz-classic-player .player .player-video {
- position: absolute;
- top: 0; bottom: 32px;
- left: 0; right: 0;
-}
-
-.ffz-classic-player .player.player-isvod .player-video {
- bottom: 36px;
-}
-
-.ffz-classic-player .player .player-controls-bottom {
- opacity: 1;
-
- padding-top: 0;
- border-top: 1px solid #000;
- border-bottom: 1px solid #000;
-
- background: -webkit-linear-gradient(bottom, #252525, #666);
- background: linear-gradient(to top, #252525, #666);
-}
-
-.ffz-classic-player .theatre .player .player-video,
-.ffz-classic-player .player[data-isfullscreen="true"] .player-video {
- bottom: 0;
-}
-
-.ffz-classic-player .theatre .player .player-controls-bottom,
-.ffz-classic-player .player[data-isfullscreen="true"] .player-controls-bottom {
- margin-bottom: -32px;
- -webkit-transition: margin-bottom .2s ease-out, padding-bottom .2s ease-out;
- transition: margin-bottom .2s ease-out, padding-bottom .2s ease-out;
-}
-
-.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .theatre .player .player-controls-bottom,
-.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .theatre .player[data-controls=true] .player-controls-bottom,
-.ffz-classic-player .theatre .player[data-isfullscreen="true"] .player-controls-bottom,
-.ffz-classic-player .theatre .player[data-isfullscreen="true"][data-controls=true] .player-controls-bottom {
- padding-bottom: 0;
-}
-
-.ffz-classic-player .theatre .player.player-isvod .player-controls-bottom,
-.ffz-classic-player .player.player-isvod[data-isfullscreen="true"] .player-controls-bottom {
- margin-bottom: -36px;
-}
-
-.ffz-classic-player .theatre .player[data-controls=true] .player-controls-bottom,
-.ffz-classic-player .player[data-isfullscreen="true"][data-controls="true"] .player-controls-bottom {
- margin-bottom: 0;
-}
-
-.ffz-classic-player:not(.ffz-top-conversations):not(.ffz-theatre-conversations) .theatre .player[data-isfullscreen=false][data-controls=true] .player-controls-bottom {
- padding-bottom: 40px;
-}
-
-.ffz-classic-player .player .player-button {
- padding-bottom: 0;
- height: 30px;
-}
-
-
-.ffz-classic-player .player .player-slider:before,
-.ffz-classic-player .player .player-button,
-.ffz-classic-player .player .player-slider .ui-slider-handle,
-.ffz-classic-player .player .player-seek .player-seek__time {
- -webkit-filter: drop-shadow(0px 0px 1px #000);
- filter: drop-shadow(0px 0px 1px #000);
-}
-
-
-.ffz-classic-player .player .player-slider .ui-slider-handle { background-color: #aeaeae; }
-.ffz-classic-player .player .player-button svg { fill: #aeaeae; }
-.ffz-classic-player .player .player-seek .player-seek__time { color: #ddd; }
-
-.ffz-player-volume .player .player-volume__slider-container,
-.ffz-classic-player .player .player-volume__slider-container {
- width: auto;
- opacity: 1;
-}
-
-
-.ffz-classic-player .player .player-seek {
- padding: 0;
-}
-
-.ffz-classic-player .player .player-seek .player-slider {
- margin: -1em 0;
-}
-
-.ffz-classic-player .player .player-seek .player-seek__time-container {
- position: absolute;
- bottom: -12px;
- left: 210px;
-}
-
-.ffz-classic-player .player .player-seek .player-seek__time + .player-seek__time:before {
- content: "/";
- padding: 0 5px;
- opacity: 0.8;
-}
-
-.player-button--close { top: 0 }
-
-/* Directory Logos */
-
-.chat-commerce-right-content__pocket--button > *,
-.item .meta .title a a,
-.item .meta .title a img { pointer-events: none }
-
-.ffz-directory-logo .card__body,
-.ffz-directory-logo .meta {
- padding-top: 5px;
-}
-
-.ffz-directory-logo .card__title,
-.ffz-directory-logo .meta p.title {
- padding-top: 4px;
- min-height: 24px;
-}
-
-.ffz-directory-logo .meta p { width: auto; }
-
-.ffz-directory-logo .profile-photo {
- float: left;
- height: 46px;
- width: 46px;
- margin-right: 10px;
-}
-
-.ffz-directory-logo .profile-photo.is-csgo {
- margin-bottom: 20px;
-}
-
-/* Flip Dashboard */
-
-.ffz-flip-dashboard #dash_main #controls_column {
- float: right;
- margin-left: 20px;
- margin-right: 0;
-}
-
-.ffz-flip-dashboard #dash_main .dash-chat-column {
- right: inherit;
- left: 0;
- margin-right: 20px;
-}
-
-/* Chat Interactions Fixes */
-
-body:not(.ffz-bttv) .ember-chat .chat-commands-dropdown {
- z-index: 998; /* 1 less than the header */
- padding: 0;
- margin-top: -1px;
- background-color: #9265d5 !important;
-}
-
-body:not(.ffz-bttv) .ember-chat .chat-commands-dropdown li {
- border: none;
-}
-
-body:not(.ffz-bttv) .ember-chat .chat-commands-dropdown li + li {
- border-top: 1px solid rgba(0,0,0,0.25);
-}
-
-body:not(.ffz-bttv) .app-main.theatre .chat-container .chat-commands-dropdown li,
-body:not(.ffz-bttv) .theme--dark .ember-chat .chat-commands-dropdown li,
-body:not(.ffz-bttv) .dark .ember-chat .chat-commands-dropdown li,
-body:not(.ffz-bttv) .ember-chat .chat-commands-dropdown li:hover {
- background-color: rgba(0,0,0,0.25) !important;
- color: #fff;
-}
-
-body:not(.ffz-bttv) .app-main.theatre .chat-container .chat-commands-dropdown li:hover,
-body:not(.ffz-bttv) .dark .ember-chat .chat-commands-dropdown li:hover,
-body:not(.ffz-bttv) .theme--dark .ember-chat .chat-commands-dropdown li:hover {
- background-color: rgba(0,0,0,0.75) !important;
- color: #fff !important;
-}
-
-
-/* Conversations */
-
-.ffz-theatre-conversations .app-main.theatre .conversations-wrapper { display: none }
-
-body:not(.ffz-bttv) .conversation-window .new-message-divider + .timestamp-line {
- margin-top: -3px;
-}
-
-/* ignore-cta is a bit silly right now */
-body:not(.ffz-bttv) .conversation-window .ignore-cta:not(.hidden) + .conversation-content {
- padding-top: 76px;
-}
-
-.conversation-window .conversation-system-message.ignore-cta-container {
- padding-right: 26px;
-}
-
-
-/* Hide that which should be hidden. */
-.conversation-window.collapsed .ignore-cta,
-.conversation-chat-line.action .colon,
-.conversation-input-bar .emoticon-selector .tabs,
-.conversation-preview-line .badges { display:none }
-
-.conversation-window.has-unread .convoHeader .username { color: #fff !important; }
-
-
-/* Top Conversations */
-
-.ffz-top-conversations .app-main.theatre .conversations-wrapper {
- bottom: auto;
-}
-
-body:not(.ffz-top-conversations) .conversations-list-bottom-bar {
- border-bottom: none;
-}
-
-.ffz-top-conversations .conversations-content {
- left: 30px;
- right: 30px;
-}
-
-.ffz-top-conversations .conversation-window,
-.ffz-top-conversations .conversations-content {
- bottom: inherit;
- top: 0px;
-}
-
-.ffz-top-conversations .conversation-window.collapsed .convoHeader {
- box-shadow: none;
-}
-
-.ffz-top-conversations .conversations-list-container.list-displayed {
- top: 243px;
-}
-
-.ffz-top-conversations .conversations-list-bottom-bar {
- border-top: none;
-}
-
-.ffz-top-conversations .conversations-content .total-unread {
- top: inherit;
- bottom: -10px;
-}
-
-body:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .theatre .player-controls-bottom .player-buttons-left,
-body:not(.ffz-theatre-conversations):not(.ffz-top-conversations) .theatre .player-controls-bottom .player-buttons-right {
- margin-bottom: 40px;
-}
-
-.ffz-theatre-conversations .theatre .player-controls-bottom,
-.ffz-theatre-conversations .theatre .player[data-controls=true] .player-controls-bottom,
-.ffz-top-conversations .theatre .player-controls-bottom,
-.ffz-top-conversations .theatre .player[data-controls=true] .player-controls-bottom {
- padding-bottom: 0;
-}
-
-.ffz-top-conversations .theatre .player-livestatus {
- top: 40px;
-}
-
-.ffz-top-conversations:not([data-current-path="user.dashboards.index"]) #directory-list { padding-top: 50px; }
-
-
-/* Minimize Conversations */
-
-/*.ffz-minimize-conversations .conversation-window,*/
-.ffz-minimize-conversations .conversations-list-container {
- transition: bottom ease-in-out 75ms, top ease-in-out 75ms, padding-bottom ease-in-out 75ms, padding-top ease-in-out 75ms;
-}
-
-.ffz-minimize-conversations .conversations-list-container,
-.ffz-minimize-conversations .conversations-content:hover {
- pointer-events: all;
-}
-
-
-.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-content:hover .conversation-window.collapsed:not(.has-unread),
-.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-content:hover .conversations-list-container:not(.list-displayed) {
- bottom: 0;
- padding-top: 0;
-}
-
-.ffz-minimize-conversations:not(.ffz-top-conversations) .conversation-window.collapsed:not(.has-unread),
-.ffz-minimize-conversations:not(.ffz-top-conversations) .conversations-list-container:not(.list-displayed) {
- bottom: -30px;
- padding-top: 30px;
-}
-
-.ffz-minimize-conversations.ffz-top-conversations .conversations-content:hover .conversation-window.collapsed:not(.has-unread),
-.ffz-minimize-conversations.ffz-top-conversations .conversations-content:hover .conversations-list-container:not(.list-displayed) {
- top: 0;
- padding-bottom: 0;
-}
-
-.ffz-minimize-conversations.ffz-top-conversations .conversation-window.collapsed:not(.has-unread),
-.ffz-minimize-conversations.ffz-top-conversations .conversations-list-container:not(.list-displayed) {
- top: -30px;
- padding-bottom: 30px;
-}
-
-
-/* Following Page */
-
-.directory_header .nav li.ffz-manage-following {
- float: right;
- margin-right: 0;
-}
-
-.user.item { position: relative; }
-
-.profile-card .overlay_info.length,
-.card__img .overlay_info.length,
-.user.item .overlay_info.length {
- position: absolute;
- top: 5px;
- right: 15px;
- z-index: 1;
-
- display: inline-block;
- cursor: pointer;
-
- padding: 0 5px;
- font-size: 11px;
- line-height: 22px;
-
- background: black;
- color: #fff;
- opacity: 0.75;
-}
-
-.profile-card .overlay_info.length,
-.card__img .overlay_info.length { right: 5px }
-
-.profile-card .overlay_info.length svg,
-.card__img .overlay_info.length svg,
-.user.item .overlay_info.length svg {
- float: left;
- margin: 3px 5px 3px 0;
-}
-
-.profile-card .overlay_info.length svg path,
-.card__img .overlay_info.length svg path,
-.user.item .actions .follow svg path,
-.user.item .overlay_info.length svg path { fill: #fff; }
-
-.user.item .actions {
- margin-top: 5px;
- display: flex;
- flex-wrap: nowrap;
-}
-
-.user.item .actions button {
- display: block;
- font-size: 11px;
- text-align: center;
- height: 25px;
- line-height: 25px;
-}
-
-.user:not(.followed) .actions .follow { width: 100%; }
-
-.user.item .actions .notifications {
- flex-grow: 1;
- padding: 0;
-}
-
-.user.item .actions button:hover,
-.user.item .actions .notifications-on {
- background-color: #6441A5;
- color: #fff !important;
-}
-
-.user.followed .actions .follow span,
-.user.item:not(.followed) .actions .notifications,
-.user:not(.followed) .actions .follow .svg-unheart,
-.user.followed .actions .follow:not(:hover) .svg-unheart,
-.user.followed .actions .follow:hover .svg-heart { display: none; }
-
-.user.followed .actions .follow {
- float: left;
- width: 25px;
- margin-right: 5px;
- background-color: #00b132;
-}
-
-.user.followed .actions .follow:hover {
- background-color: #d44949;
-}
-
-.user.item .actions .follow svg {
- margin: 4px 0 -5px -5.5px;
-}
-
-/* Creative Directory */
-
-/* Make sure the fancy banner art goes all the way up */
-.ffz-top-conversations .ct-banner-container { margin-top: -50px }
-
-body:not(.ffz-tags-on-channel) .tw-title--tall { height: 60px }
-
-/*body:not(.ffz-tags-on-channel) #broadcast-meta .ct-tags,*/
-.cn-metabar > div:first-child .ct-tags,
-.cn-metabar__title .ct-tags,
-/*body:not(.ffz-creative-showcase) .ct-banner-handler .tower > div:last-child,*/
-body:not(.ffz-creative-showcase) .ct-spotlight-container { display: none; }
-
-
-
-/* Content-Box~! Down with Twitch randomly changing the box-sizing for everything! */
-
-#ffz-channel-table *,
-#ffz-group-table *,
-.ffz-ui-popup * {
- box-sizing: content-box;
-}
-
-
-/* Draggable Stats on Player */
-
-.player .ui-draggable.player-playback-stats { cursor: move }
-.player .ui-draggable.player-playback-stats li { cursor: default }
-
-
-/* FFZ Suggestions */
-
-.ember-chat .chat-interface .suggestions.ffz-suggestions {
- padding: 5px 0;
- position: absolute;
- bottom: 43px;
- left: 6px;
- width: 278px;
- background-color: white;
- border: 1px solid rgba(0, 0, 0, 0.2);
- background-clip: padding-box;
-z-index: 100
-}
-.ember-chat .chat-interface .suggestions.ffz-suggestions .suggestion {
- padding: 0 10px;
- line-height: 24px;
- width: 270px;
- font-size: 12px
-}
-.ember-chat .chat-interface .suggestions.ffz-suggestions .suggestion.disabled {
- color: #706a7c;
-font-weight: 400
-}
-.ember-chat .chat-interface .suggestions.ffz-suggestions .suggestion.disabled:hover {
- background-color: transparent;
-color: #706a7c
-}
-.ember-chat .chat-interface .suggestions.ffz-suggestions .highlighted {
- color: white;
- background-color: #6441a4;
- -webkit-transition: all ease-in-out 0.1s;
- -webkit-backface-visibility: hidden;
- transition: all 0.1s ease-in-out
-}
-
-.ffz-dark .ffz-suggestions,
-.theatre .ffz-suggestions,
-.dark .ffz-suggestions,
-.theme--dark .ffz-suggestions {
- background-color: #101010 !important;
- border-color: rgba(255,255,255,0.2) !important;
-}
-
-.conversation-input-bar .ffz-suggestions {
- padding: 5px 0;
- position: absolute;
- bottom: 30px;
- left: 6px;
- width: 268px;
- background-color: white;
- border: 1px solid rgba(0,0,0,0.2);
- background-clip: padding-box;
- z-index: 100;
-}
-
-.conversation-input-bar .ffz-suggestions .suggestion {
- padding: 0 10px;
- line-height: 24px;
- font-size: 12px;
-}
-
-.conversation-input-bar .ffz-suggestions .highlighted {
- color: #fff;
- background-color: #6441a4;
-}
-
-.suggestions.ffz-suggestions .suggestion {
- width: auto !important;
- background-repeat: no-repeat;
- background-position: calc(100% - 10px) center;
-}
-
-.suggestions.ffz-suggestions .suggestion.has-info {
- height: 40px;
- line-height: 20px;
- padding-top: 2px;
-}
-
-.ffz-suggestions .suggestion div,
-.ffz-suggestions .suggestion span {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.ffz-suggestions .suggestion.is-emoji {
- background-size: 18px;
-}
-
-.ffz-suggestions .suggestion i { opacity: 0.5 }
-
-.ffz-suggestions .suggestion.has-info span {
- opacity: 0.5;
- display: block;
- margin-top: -4px;
- font-size: 10px;
-}
-
-.suggestions.ffz-suggestions .suggestion.has-image:not(.has-info) {
- line-height: 40px;
-}
-
-/* Banned and Spoiler Games */
-
-body:not([data-current-path^="directory.csgo"]):not([data-current-path^="directory.game"]):not([data-current-path^="directory.creative"]) .ffz-game-banned { display: none !important }
-
-.ffz-game-spoilered .card__img > img,
-.ffz-game-spoilered .card__img a:not(.card__boxpin) img,
-.ffz-game-spoilered .thumb .cap img { visibility: hidden }
-
-.ffz-game-spoilered .card__img a:not(.card__boxpin):after,
-.ffz-game-spoilered .thumb .cap:after {
- position: absolute;
- top: 0; left: 0;
- bottom: 0; right: 0;
- background: url("https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg") no-repeat #64439a;
- background-size: cover;
- content: '';
-}
-
-.directory_header .ffz-block-button,
-.directory_header .ffz-spoiler-button {
- margin-left: 1rem;
- vertical-align: top;
-}
-
-.balloon .balloon__title button {
- display: block;
-}
-
-.balloon .balloon__title button:not(:last-of-type) {
- margin-bottom: 1rem;
-}
-
-body[data-current-path^="directory.creative"] .follow-button.ffz-block-button {
- margin: 0 1rem;
-}
-
-.button.ffz-spoiler-button,
-.button.ffz-block-button {
- background: #555;
-}
-
-.button.ffz-spoiler-button.active {
- background: #006700;
-}
-
-.button.ffz-spoiler-button:not(.disabled):hover {
- background: #247324;
-}
-
-.button.ffz-block-button.active {
- background: #973333;
-}
-
-.button.ffz-block-button:not(.disabled):hover {
- background: #a94444;
-}
-
-
-/* Dashboard Channel Feed */
-
-.activity-card .emoticon-selector .tabs { display: none }
-
-.ffz-feed-button span { line-height: 30px; }
-#ffz-feed-tabs .tabs { margin-bottom: 10px }
-#ffz-feed-tabs textarea { resize: vertical }
-
-.ffz-feed-button .char-count.over-limit { color: #A00 }
-.ffz-feed-button .char-count.over-limit span { display: inline }
-.ffz-feed-button .char-count span { display: none; opacity: 0.75 }
-.ffz-feed-button .char-count,
-.ffz-feed-button button { float: right; margin-left: 10px }
-
-body.ffz-bttv #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #6441a5; }
-body.ffz-bttv-dark #ffz-feed-tabs .tabs li:not(.selected):not(:hover) a { color: #999; }
-
-body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 }
-
-/* New Sidebar */
-
-.warp__anchor { height: 5.5rem }
-
-
-/* Chat Pane Overhaul */
-
-.ember-chat.ffz-chat-pane .chat-messages { bottom: 0 }
-
-/* Hide Outlines */
-
-.convoHeader,
-.conversations-list-icon,
-.toggle-notification-menu {
- outline: none !important
-}
-
-
-/* Badges */
-
-.badges .badge {
- background-size: 18px 18px !important;
-}
-
-.convoHeader .badges .badge {
- background-size: 14px 14px !important;
- -webkit-mask-size: 14px 14px;
- mask-size: 14px 14px;
-}
-
-/*.badges .badge {
- height: 18px;
- min-width: 18px;
- display: inline-block;
- vertical-align: middle;
- float: left;
- margin: 1px 3px 1px 0
-}
-
-.badges .broadcaster {
- background: #e71818 url(/images/xarth/badge_broadcaster.svg);
- background-size: 100%
-}
-
-.badges .turbo {
- cursor: pointer;
- background: #6441a5 url(/images/xarth/badge_turbo.svg);
- background-size: 100%
-}
-
-.badges .subscriber {
- cursor: pointer
-}
-
-.badges .staff {
- background: #200f33 url(/images/xarth/badge_staff.svg);
- background-size: 100%
-}
-
-.badges .global-moderator {
- background: #0c6f20 url(/images/xarth/badge_globalmod.svg);
- background-size: 100%
-}
-
-.badges .moderator {
- background: #34ae0a url(/images/xarth/badge_mod.svg);
- background-size: 100%
-}
-
-.badges .admin {
- background: #faaf19 url(/images/xarth/badge_admin.svg);
- background-size: 100%
-}*/
-
-/* Button Fix */
-
-.ffz-no-bg.button,
-.ffz-no-bg {
- background: transparent;
-}
-
-.ffz-hidden-badges.badges { margin-left: 5px }
-
-
-/* Odd Badges */
-.badge.click_url { cursor: pointer }
-
-.ffz-dark .badge.staff.version-1.colored,
-.theatre .badge.staff.version-1.colored,
-.dark .badge.staff.version-1.colored,
-.theme--dark .badge.staff.version-1.colored {
- background: linear-gradient(#6441a4,#6441a4);
-}
-
-/*.badge.premium.version-1:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/premium/1/1.png") #009cdc;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/premium/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/premium/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/premium/1/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/premium/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/premium/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/premium/1/4.png") 4x);
-}
-
-.badge.premium.version-1.colored {
- background: linear-gradient(#009cdc,#009cdc);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/premium/1/1.png");
-}
-
-.badge.bits.version-1:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") #cbc8d0;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1/4.png") 4x);
-}
-
-.badge.bits.version-1.colored {
- background: linear-gradient(#cbc8d0,#cbc8d0);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/1/1.png");
-}
-
-.badge.bits.version-100:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") #ca7eff;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100/4.png") 4x);
-}
-
-.badge.bits.version-100.colored {
- background: linear-gradient(#ca7eff,#ca7eff);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/100/1.png");
-}
-
-.badge.bits.version-1000:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") #3ed8b3;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/4.png") 4x);
-}
-
-.badge.bits.version-1000.colored {
- background: linear-gradient(#3ed8b3,#3ed8b3);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/1000/1.png");
-}
-
-.badge.bits.version-5000:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") #49acff;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/4.png") 4x);
-}
-
-.badge.bits.version-5000.colored {
- background: linear-gradient(#49acff,#49acff);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/5000/1.png");
-}
-
-.badge.bits.version-10000:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") #ff271e;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/4.png") 4x);
-}
-
-.badge.bits.version-10000.colored {
- background: linear-gradient(#ff271e,#ff271e);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/10000/1.png");
-}
-
-.badge.bits.version-100000:not(.colored) {
- background: url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") #ffcb13;
- background-image: -webkit-image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/4.png") 4x);
- background-image: image-set(url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png") 1x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/2.png") 2x, url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/4.png") 4x);
-}
-
-.badge.bits.version-100000.colored {
- background: linear-gradient(#ffcb13,#ffcb13);
- -webkit-mask-image: url("https://cdn.frankerfacez.com/badges/twitch/bits/100000/1.png");
-}*/
-
-/* New Resub Banner */
-
-.top-notification--resub {
- position: relative;
- z-index: 10;
- pointer-events: none;
-}
-
-.top-notification--resub > * {
- pointer-events: auto;
-}
-
-.sticky-message {
- padding: 5px 15px;
-}
-
-.chat-room .show-mod-icons .chat-lines .chat-line:not(.admin) .mod-icons {
- display: block !important;
- position: absolute;
- top: 0; bottom: 0;
- padding: 3px 5px 0;
- left: 0;
- z-index: 99;
- background-color: rgba(255,255,255,0.8);
-}
-
-.theatre .chat-room .show-mod-icons .chat-lines .chat-line:not(.admin) .mod-icons,
-.dark .chat-room .show-mod-icons .chat-lines .chat-line:not(.admin) .mod-icons,
-.theme--dark .chat-room .show-mod-icons .chat-lines .chat-line:not(.admin) .mod-icons {
- background-color: rgba(0,0,0,0.8);
-}
-
-
-/* Bits */
-
-.bits-card { z-index: 10 }
-
-.ffz-bit {
- display: inline-block;
- height: 28px;
- line-height: 28px;
- font-weight: bold;
- padding-left: 30px;
- margin: -5px 5px;
- box-sizing: content-box;
- background-position: 0;
-}
-
-.tipsy .ffz-bit {
- margin: 0 5px;
-}
-
-.ffz-bit:not(.inventory-bits__image):after {
- content: attr(data-amount);
-}
-
-.theatre .bits-purchase__header,
-.dark .bits-purchase__header,
-.theme--dark .bits-purchase__header {
- color: #acacbf;
-}
-
-.theatre .bits-card,
-.app-main.theatre .chat-container .bits-card, /* Twitch's CSS is retarded */
-.theme--dark .bits-card,
-.dark .bits-card,
-
-.theatre .bits-purchase__row,
-.theme--dark .bits-purchase__row,
-.dark .bits-purchase__row,
-
-.theatre .bits-footer,
-.theme--dark .bits-footer,
-.dark .bits-footer {
- border-color: rgba(255,255,255,0.2);
- color: #ccc;
-}
-
-
-/* New Chat Formatting */
-
-.ember-chat .chat-messages .timestamp { margin-right: 0; }
-.badges { display: inline-block }
-.badges .badge {
- float: none;
- margin: -1px 3px 0 0;
-}
-
-.ffz-padded-emoticons .activity-body .emoticon,
-.ffz-padded-emoticons .chat-line .emoticon {
- margin: -1px 0 0;
-}
-
-.ffz-baseline-emoticons .activity-body .emoticon,
-.ffz-baseline-emoticons .chat-line .emoticon {
- vertical-align: baseline;
- padding-top: 5px;
-}
-
-.ffz-baseline-emoticons .activity-body .emoticon.emoji,
-.ffz-baseline-emoticons .chat-line .emoticon.emoji {
- padding-top: 0px;
-}
-
-
-/* Settings Filtering */
-
-.ffz-ui-menu-page .ffz-filter-container {
- padding: 10px;
- border-top: 1px solid rgba(0,0,0,0.2);
-}
-
-.ffz-ui-menu-page input.js-filter-input {
- margin: 0;
- width: calc(100% - 2rem);
-}
-
-.bttv-incompatibility b + b:before {
- content: ', ';
- font-weight: normal;
-}
-
-/* Chat Rules */
-
-.ember-chat .chat-interface .chat-rules {
- z-index: 1;
-}
-
-.theatre .chat-interface .chat-rules,
-.dark .chat-container .chat-rules
-.theme--dark .chat-container .chat-rules {
- background: #101010;
- color: #ccc;
- border-color: rgba(255,255,255,0.2);
-}
-
-.ffz-mod-balloon {
- display: block;
- max-width: 200px;
-}
-
-.ffz-mod-balloon .ffz-title {
- padding: 0 1rem;
-}
-
-
-.cn-metabar__ffz svg { fill: #898395; }
-.cn-metabar__ffz {
- cursor: default;
- color: #706a7c;
- font-size: 1.4rem;
-}
-.cn-metabar__ffz figure {
- margin-right: 5px;
-}
-
-.ffz-channel-title-top .cn-metabar__more { width: 100% }
-.ffz-channel-title-top #channel {
- position: relative;
- padding-top: 80px;
-}
-
-.ffz-channel-title-top:not(.ffz-minimal-channel-bar):not(.ffz-channel-bar-bottom) #channel {
- padding-top: 85px;
-}
-
-
-.cn-hosting .ffz.card__layout .card__title { color: #9c9c9c }
-
-.cn-hosting .ffz.card__layout .card__title,
-.ffz-channel-title-top .cn-metabar__more,
-.app-main.theatre .cn-metabar__more {
- margin-top: 0 !important;
- padding-top: 0 !important;
- border-top: none !important;
-}
-
-.ffz-hide-channel-banner .cn-cover { height: 0 !important }
-.ffz-minimal-channel-title.ffz-channel-title-top #channel { padding-top: 50px }
-
-.ffz-minimal-channel-title.ffz-channel-title-top:not(.ffz-minimal-channel-bar):not(.ffz-channel-bar-bottom) #channel {
- padding-top: 50px;
-}
-
-.ffz-channel-title-top .ffz-channel.ffz-host { margin-top: -55px }
-.ffz-minimal-channel-title.ffz-channel-title-top .ffz-channel.ffz-host { margin-top: -25px }
-
-.ffz-channel-title-top .cn-metabar > div:first-child,
-.ffz-channel-title-top .cn-metabar__title {
- position: absolute;
- top: 5px;
- padding: 1rem 0;
- z-index: 3;
-}
-
-.ffz-channel-title-top:not(.ffz-channel-bar-bottom):not(.ffz-minimal-channel-bar) #channel.ffz-bar-fixed .cn-metabar > div:first-child,
-.ffz-channel-title-top:not(.ffz-channel-bar-bottom):not(.ffz-minimal-channel-bar) #channel.ffz-bar-fixed .cn-metabar__title {
- margin-top: 50px;
-}
-
-.ffz-minimal-channel-bar .cn-bar-fixed {
- top: -40px;
- transition: top ease-in-out 75ms, bottom ease-in-out 75ms;
-}
-
-.ffz-sidebar-minimize:not(.ffz-minimal-channel-bar) .has-sc .cn-bar-fixed { top: 10px }
-
-.ffz-minimal-channel-bar .cn-bar-fixed:hover { top: 0 }
-.ffz-minimal-channel-bar .has-sc .cn-bar-fixed { top: 10px }
-
-.ffz-sidebar-minimize.ffz-minimal-channel-bar:not(.ffz-channel-bar-bottom) .has-sc .cn-bar-fixed { top: -30px }
-.ffz-sidebar-minimize.ffz-minimal-channel-bar:not(.ffz-channel-bar-bottom) .has-sc div:hover + .app-body .cn-bar-fixed { top: 10px }
-.ffz-sidebar-minimize.ffz-minimal-channel-bar:not(.ffz-channel-bar-bottom) .has-sc .cn-bar-fixed:hover { top: 10px }
-
-.ffz-channel-bar-bottom .player-mini { bottom: 20px }
-
-.ffz-minimal-channel-bar:not(.ffz-channel-bar-bottom) .has-sc div:hover + .app-body .cn-bar-fixed,
-.ffz-minimal-channel-bar:not(.ffz-channel-bar-bottom) .has-sc .cn-bar-fixed:hover { top: 50px }
-.ffz-minimal-channel-bar.ffz-channel-bar-bottom .cn-bar-fixed { top: auto; bottom: -40px }
-.ffz-minimal-channel-bar.ffz-channel-bar-bottom .cn-bar-fixed:hover { bottom: 0 }
-
-.ffz-sidebar-minimize .top-nav {
- top: -40px !important;
- transition: top ease-in-out 75ms, bottom ease-in-out 75ms;
-}
-
-.ffz-sidebar-minimize .app-main.has-sc:not(.theatre) { top: 10px }
-.ffz-sidebar-minimize .top-nav:hover { top: 0 !important }
-.ffz-sidebar-minimize .has-sc .js-player-persistent { margin-top: 40px }
-
-/*.ffz-minimal-channel-title .cn-metabar,
-.ffz-channel-title-top .cn-metabar {
- min-height: 70px;
-}*/
-
-.ffz-hide-channel-banner .cn-bar__avatar-wrap { width: 4rem; height: 4rem; }
-
-/* Channel Metadata Sorting */
-
-div.metadata-box {
- border: none !important;
- margin: 0 !important;
- padding: 1rem 0 !important;
- background: transparent !important;
-}
-
-.cn-hosting--bottom > *,
-.cn-metabar__more > * {
- margin: .5rem 1rem .5rem 0 !important;
- order: 49;
-}
-
-.cn-hosting--bottom > *,
-.cn-metabar__more > span { order: 150 }
-
-.cn-hosting--bottom:after,
-.cn-metabar__more:after {
- content: '';
- order: 75;
- flex-grow: 1;
-}
-
-.cn-hosting--bottom { padding-right: 0 }
-#ffz-metadata-popup .button { margin: 0 }
-
-.cn-hosting--bottom .button:not(.follow-button),
-.ffz-channel-options .qa-channel-options,
-.ffz-share-box .qa-share-box__button { margin: 0 !important }
-
-.cn-metabar__more > .cn-metabar__livecount { order: 1 }
-/*.cn-metabar__more > .cn-metabar__ffz[data-key="uptime"] { order: 2 }
-.cn-metabar__more > #ffz-chatter-display { order: 51 }
-.cn-metabar__more > #ffz-player-stats { order: 3 }*/
-.cn-metabar__more > .cn-metabar__viewcount { order: 50; flex-grow: 0 !important }
-
-.cn-metabar__more > .ffz-channel-broadcast-link { order: 100 }
-.cn-metabar__more > .ffz-share-box { order: 101 }
-.cn-metabar__more > .ffz-channel-options { order: 999; margin-right: 0 !important }
-
-.cn-hosting--bottom > .follow-button { order: 1 }
-.cn-hosting--bottom > .follow-button.is-following { margin-right: 0 !important }
-.cn-hosting--bottom > .notification-controls { order: 1 }
-.cn-hosting--bottom > span.ember-view { order: 2 }
-
-.cn-hosting--bottom > .ffz-channel-options { flex-grow: 0 }
-.cn-hosting--bottom .cn-hosting__options { box-shadow: inset 0 0 0 1px rgba(100,65,164,.5) }
-.cn-hosting--bottom .cn-hosting__options:focus {
- box-shadow: 0 0 6px 0 #7d5bbe,inset 0 0 0 1px rgba(100,65,164,.5) }
-
-.cn-metabar__ffz[data-key="viewers"] svg { fill: #fc3636 }
-.cn-metabar__ffz[data-key="viewers"] { color: #fc3636 }
-
-.ffz-dark .pl-button--hollow:hover,
-.ffz-dark .button--hollow:hover,
-.cn-hosting--bottom .button-hollow {
- background-color: #2c2541;
-}
-
-
-/* Mod Card Notes */
-
-.ffz-moderation-card .note-input textarea {
- width: 100%;
- height: 50px;
- resize: none;
- margin-bottom: 10px;
-}
-
-.ffz-moderation-card .mod-icons .delete-note {
- background-image: url("//cdn.frankerfacez.com/script/button_ban.svg");
- background-repeat: no-repeat;
-}
-
-.ffz-moderation-card .mod-icons .edit-note {
- background-image: url("//cdn.frankerfacez.com/script/button_edit.svg");
- background-repeat: no-repeat;
-}
-
-/* Ban Notices */
-
-.theatre .chat-history .chat-line.notification .message,
-.theme--dark .chat-history .chat-line.notification .message,
-.dark .chat-history .chat-line.notification .message,
-
-.theatre .chat-history .chat-line.admin .message,
-.theme--dark .chat-history .chat-line.admin .message,
-.dark .chat-history .chat-line.admin .message,
-
-.theatre .chat-messages .chat-line.admin .message,
-.theme--dark .chat-messages .chat-line.admin .message,
-.dark .chat-messages .chat-line.admin .message,
-
-.theatre .chat-messages .chat-line.notification .message,
-.theme--dark .chat-messages .chat-line.notification .message,
-.dark .chat-messages .chat-line.notification .message {
- color: #777
-}
-
-.ban-tip { border-bottom: 1px dotted rgba(102,102,102,0.5); }
-
-.ffz-clickable-mentions .ffz-recent-messages .chat-line .user-token,
-.ffz-clickable-mentions .pinned-cheers .chat-line .user-token,
-.ffz-clickable-mentions .chat-display .chat-line .user-token { font-weight: bold }
-.ffz-clickable-mentions .ffz-recent-messages .chat-line .user-token:hover,
-.ffz-clickable-mentions .pinned-cheers .chat-line .user-token:hover,
-.ffz-clickable-mentions .chat-display .chat-line .user-token:hover {
- text-decoration: underline;
- cursor: pointer;
-}
-
-
-/* Following Menu */
-
-#ffz-metadata-popup .scroller {
- max-height: 420px;
- overflow-y: scroll;
- overflow-x: hidden;
- margin: -1rem;
- padding: 1rem;
-}
-
-/* Remove BTTV's ugly styling */
-.ffz-following-row .button {
- color: #fff !important;
- box-shadow: none !important;
-}
-
-.ffz-following-row {
- padding: 1rem 0;
- line-height: 30px;
- border-bottom: 1px solid rgba(0,0,0,0.2);
-}
-
-.ffz-following-row:first-child { padding-top: 0 }
-.ffz-following-row:last-child { padding-bottom: 0; border-bottom: none }
-
-.ffz-following-row .image:not([src]) { visibility: hidden }
-
-.ffz-following-row .image {
- display: inline-block;
- height: 30px;
- width: 30px;
- margin-right: 1rem;
-}
-
-.ffz-following-row .right,
-.ffz-following-row .button {
- float: right;
-}
-
-.ffz-following-row .switch {
- float: right;
- margin: 7px 1rem 0 0;
-}
-
-/* Schedule */
-
-.ffz-schedule-row {
- position: relative;
- margin: 0 -1rem;
- padding: 1rem 1rem 1rem 6rem;
- border-bottom: 1px solid rgba(0,0,0,0.2);
-}
-
-.ffz-schedule-row:first-child { margin-top: -1rem }
-.ffz-schedule-row:last-child { margin-bottom: -1rem; border-bottom: none }
-
-.ffz-schedule-row.ffz-old-item { background-color: rgba(0,0,0,0.15); opacity: 0.6 }
-
-.ffz-schedule-date { background-color: rgba(127,127,127,0.1) }
-.ffz-schedule-row.ffz-current-item { background-color: rgba(0,0,0,0.2) }
-
-.ffz-dark .ffz-schedule-row.ffz-old-item,
-.theatre .ffz-schedule-row.ffz-old-item {
- background-color: rgba(255,255,255,0.1)
-}
-
-.ffz-dark .ffz-schedule-row.ffz-current-item,
-.theatre .ffz-schedule-row.ffz-current-item {
- background-color: rgba(255,255,255,0.15)
-}
-
-.ffz-schedule-row .heading h2 span,
-.ffz-schedule-row .runners,
-.ffz-schedule-row .meta,
-.ffz-schedule-date {
- color: #666;
-}
-
-
-.ffz-dark .ffz-schedule-row .heading h2 span,
-.theatre .ffz-schedule-row .heading h2 span,
-.ffz-dark .ffz-schedule-row .runners,
-.theatre .ffz-schedule-row .runners,
-.ffz-dark .ffz-schedule-row .meta,
-.theatre .ffz-schedule-row .meta,
-.ffz-dark .ffz-schedule-date,
-.theatre .ffz-schedule-date {
- color: #999;
-}
-
-
-.ffz-schedule-row .runners span { color: #000 }
-
-.ffz-dark .ffz-schedule-row .runners span,
-.theatre .ffz-schedule-row .runners span { color: #fff }
-
-.ffz-schedule-row .meta {
- margin-bottom: 5px;
-}
-
-.ffz-schedule-row .heading h2 {
- padding: 0;
- margin: 0 0 5px;
- font-size: 2rem;
- line-height: 1em;
-}
-
-.ffz-schedule-row .heading h2 span {
- font-size: 1.5rem;
- line-height: 1em;
- white-space: nowrap;
-}
-
-.ffz-schedule-row .time-start {
- position: absolute;
- left: 1rem;
- width: 45px;
-
- font: 16px Sans-Serif;
-}
-
-.ffz-schedule-row .time-start { top: 1rem }
-
-.ffz-schedule-date {
- padding-left: 0;
- text-align: center;
- font: bold 1.1rem Sans-Serif;
-}
-
-
-/* Hidden Navigation */
-
-.ffz-sidebar-minimize:not(.ffz-sidebar-swap) #left_col.closed:not(:hover) { left: -40px }
-.ffz-sidebar-minimize:not(.ffz-sidebar-swap) #left_col.open:not(:hover) { left: -230px }
-.ffz-sidebar-minimize:not(.ffz-sidebar-swap) .app-main:not(.has-sc) #main_col { margin-left: 10px !important }
-.ffz-sidebar-minimize:not(.ffz-sidebar-swap) .app-main:not(.has-sc) #left_col ~ #main_col .cn-bar-fixed { left: 10px }
-
-.ffz-sidebar-minimize.ffz-sidebar-swap #left_col.closed:not(:hover) { right: -40px }
-.ffz-sidebar-minimize.ffz-sidebar-swap #left_col.open:not(:hover) { right: -230px }
-.ffz-sidebar-minimize.ffz-sidebar-swap .app-main:not(.has-sc) #main_col { margin-right: 10px !important }
-.ffz-sidebar-minimize.ffz-sidebar-swap .app-main:not(.has-sc) .cn-bar-fixed { right: 10px }
-
-
-/* Modifier Emotes */
-
-.emoticon-grid span.emoticon:not([data-ffz-emote="59829"]) {
- padding-top: 0;
- margin: 0 !important
-}
-
-.modified-emoticon ~ .emoticon { position: relative; z-index: 1}
-
-.modified-emoticon {
- position: relative;
- z-index: 0;
-}
-
-.ffz-bttv .emoticon[data-ffz-emote="70864"]:hover { opacity: 0 }
-
-.modified-emoticon[data-ffz-emote="59829"] span { right: 128px }
-.modified-emoticon > img.emoticon { margin: 0 !important }
-
-.emoticon[data-ffz-modifiers~="147011"] { clip-path: circle(14px at 50% 50%) }
-.modified-emoticon > img.emoticon[data-ffz-modifiers~="147038"] {
- transform: translateY(-2px);
- max-height: 24px;
- clip-path: circle(14px at 50% 50%);
-}
-
-.modified-emoticon:hover span .emoticon[data-ffz-emote="70864"],
-.modified-emoticon:not(:hover) .emoticon[data-ffz-modifiers~="70864"] { opacity: 0 }
-
-.modified-emoticon span {
- position: absolute;
- top: -20px; bottom: -20px; left: -20px; right: -20px;
- margin: auto;
- pointer-events: none;
-}
-
-.modified-emoticon span img {
- position: absolute;
- top: 50%; left: 50%;
- transform: translate(-50%, -50%);
-}
-
-.ffz-bttv .modified-emoticon span,
-.ffz-baseline-emoticons .modified-emoticon span {
- top: -25px;
-}
-
-body:not(.ffz-bttv) .modified-emoticon span,
-.ffz-bttv .modified-emoticon { pointer-events: none }
-.ffz-bttv .modified-emoticon > .emoticon { pointer-events: all }
-
-.ffz-bttv .chat-line .modified-emoticon,
-.ffz-baseline-emoticons .chat-line .modified-emoticon {
- padding-top: 0;
- margin-bottom: -3px;
- vertical-align: bottom !important;
-}
-
-body:not(.ffz-sidebar-swap) .app-main.theatre #main_col:not(.expandRight) #player[data-isfullscreen=true] { right: 0 !important }
-body.ffz-sidebar-swap .app-main.theatre #main_col:not(.expandRight) #player[data-isfullscreen=true] { left: 0 !important }
-
-.pinned-cheers__message-wrapper .chat-line:before,
-.pinned-cheers__message-wrapper:before { display: none }
-
-.pinned-cheers .chat-line { background-color: transparent !important }
-
-.pinned-cheers__message-wrapper .chat-line { margin: 10px 0; padding: 0 }
-.pinned-cheers .mod-icons { display: inline !important }
-
-.pinned-cheer__top-bar,
-.pinned-cheers--padding { padding: 0 !important }
-
-/* Player Mini Positioning Stuff */
-
-.ffz-sidebar-swap .expandRight .player-mini {
- left: 10px !important;
-}
-
-/* New Dashboard */
-
-.dash-chat {
- font-size: 1.2rem;
- width: calc(100% - 2px);
- height: calc(100% - 2px);
-}
-
-.dash-column {
- margin-right: calc(1rem - 5px);
- padding-right: 5px;
-}
-
-@media(min-width: 1200px) {
- .dash-column {
- margin-right: calc(1.5rem - 5px);
- }
-}
-
-
-/* ITAD */
-
-.button.button--large + .ffz-price-info > .itad-button {
- font-size: 1.6rem;
- line-height: 3.6rem;
- padding: 0 1.2rem;
-}
-
-.itad-button {
- background-color: #046eb2;
- overflow: hidden;
-}
-
-.itad-button .button__num-block {
- margin: 0 -3rem 0 0;
- padding-right: 3rem !important;
- display: inline-block;
-}
-
-.button.button--large + .ffz-price-info > .itad-button .button__num-block {
- padding-right: 4.8rem !important;
-}
-
-.itad-button:focus,
-.itad-button:hover {
- background-color: #1587cf;
-}
-
-.itad-button:focus { box-shadow: 0 0 6px 0 #5c99bf, inset 0 0 0 1px #9ec2d9 }
-
-.itad-balloon table {
- width: 100%;
- border-spacing: 0;
-}
-
-.itad-balloon th { border-bottom: 1px solid rgba(0,0,0,0.2) }
-.itad-balloon td { padding: 4px 0 0 }
-
-.itad-balloon span {
- display: block;
- /*border-top: 1px solid rgba(0,0,0,0.2);
- margin-top: 5px;
- padding-top: 5px;*/
-}
-
-.ffz-dark .itad-balloon th,
-.ffz-dark .itad-balloon span {
- border-color: rgba(255,255,255,0.2);
-}
-
-.itad-balloon table th:not(:first-child),
-.itad-balloon table td:not(:first-child) { text-align: right }
-
-.ffz-has-recent-messages .resub-wrap {
- padding-top: 30px;
-}
-
-.chat-messages[data-pinned_height="30"],
-.ffz-has-recent-messages .sticky-message,
-.ffz-has-recent-messages .chat-messages {
- top: 30px;
-}
-
-.chat-messages[data-pinned_height="76"] { top: 76px }
-.ffz-has-recent-messages .chat-messages[data-pinned_height="30"] { top: 60px }
-.ffz-has-recent-messages .chat-messages[data-pinned_height="76"] { top: 106px }
-
-.ffz-recent-messages {
- background-color: #efeef1;
- border-bottom: 1px solid #dad8de;
- z-index: 1000;
- overflow: hidden;
- position: absolute !important;
- top: 0;
- left: 0;
- right: 0;
- transition: height ease-in-out 100ms;
- height: 30px;
- box-shadow: 0 5px 5px -3px rgba(239,238,241,0.5);
-}
-
-.ffz-recent-messages.ui-moved {
- height: auto;
- right: auto;
- width: 340px;
- border: 1px solid #dad8de;
-}
-
-.ffz-recent-messages.ui-moved .pill {
- display: none;
-}
-
-.ffz-recent-messages .ffz-recent-expando {
- background-color: #efeef1;
- padding: 5px 10px;
- border-bottom: 1px solid #dad8de;
- color: #333;
- cursor: move;
- position: relative;
- z-index: 1;
-}
-
-.ffz-recent-expando .pill {
- float: right;
- font-size: 100%;
- margin-top: 3px;
-}
-
-.ffz-recent-messages .chat-history {
- top: -150px;
- position: relative;
- height: 150px;
- transition: top ease-in-out 75ms;
-}
-
-.ffz-recent-messages:not(.ui-moved):hover {
- height: 180px;
-}
-
-.ffz-recent-messages.ui-moved .chat-history,
-.ffz-recent-messages:not(.ui-moved):hover .chat-history {
- top: 0px;
-}
-
-.ffz-recent-messages.ui-moved .chat-history {
- min-height: 150px;
- height: auto;
-}
-
-.ffz-dark .ffz-recent-expando,
-.theatre .ffz-recent-expando,
-.theme--dark .ffz-recent-expando,
-.dark .ffz-recent-expando,
-.force-dark .ffz-recent-expando {
- background-color: #242424;
- border-color: #474747;
- color: #ccc;
-}
-
-.ffz-dark .ffz-recent-messages,
-.theatre .ffz-recent-messages,
-.theme--dark .ffz-recent-messages,
-.dark .ffz-recent-messages,
-.force-dark .ffz-recent-messages {
- background-color: #191919;
- border-color: #474747;
- box-shadow: 0 5px 5px -3px rgba(25,25,25,0.5);
-}
-
-
-/*
- H O T * G A R B A G E
-
- ___/-\___ ___/-\___ ___/-\___
-|---------| |---------| |---------|
- | | | | | | | | | |
- | | | | | | | | | | | |
- | | | | | | | | | | | |
- | | | | | | | | | | | | | | |
- |_______| |_______| |_______|
-
-*/
-
-/*#ffz-chat-menu.ui-draggable-dragging {
- height: auto !important;
- width: auto !important;
- background: transparent;
- border: none;
- padding: 0;
- display: block;
- max-width: inherit;
-}
-
-.moderation-card.ui-draggable-dragging {
- position: fixed;
- height: auto !important;
- width: auto !important;
- background: black;
- padding: 0;
- display: block;
- max-width: 340px;
-}*/
-
-.app-main.theatre .player-whispers-padding {
- margin-bottom: 0;
-}
-
-.app-main.theatre .conversations-wrapper {
- background: transparent;
-}
\ No newline at end of file
diff --git a/styles/chat.scss b/styles/chat.scss
new file mode 100644
index 00000000..00349e16
--- /dev/null
+++ b/styles/chat.scss
@@ -0,0 +1,58 @@
+.ffz-cheer {
+ display: inline-block;
+ height: 2.8rem;
+ line-height: 2.8rem;
+ padding-left: 3rem;
+ margin: -.5rem .5rem;
+ background-position: 0;
+ background-repeat: no-repeat;
+
+ &:after {
+ font-weight: bold;
+ content: attr(data-amount)
+ }
+}
+
+.ffz-cheer-preview {
+ height: 11.2rem;
+ width: 11.2rem;
+}
+
+
+.ffz-badge {
+ display: inline-block;
+ min-width: 1.8rem;
+ height: 1.8rem;
+ margin: 0 .3rem .2rem 0;
+ background-position: center;
+ vertical-align: middle;
+}
+
+
+.modified-emote {
+ ~ .chat-line__message--emote {
+ position: relative;
+ z-index: 1
+ }
+
+ position: relative;
+ z-index: 0;
+
+ > img {
+ margin: 0 !important
+ }
+
+ span {
+ position: absolute;
+ top: -20px; bottom: -20px; left: -20px; right: -20px;
+ margin: auto;
+ pointer-events: none;
+
+ img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+}
\ No newline at end of file
diff --git a/styles/icons.scss b/styles/icons.scss
new file mode 100644
index 00000000..be654765
--- /dev/null
+++ b/styles/icons.scss
@@ -0,0 +1,91 @@
+@font-face {
+ font-family: 'ffz-fontello';
+ src: url('~res/font/ffz-fontello.woff2') format('woff2'),
+ url('~res/font/ffz-fontello.woff') format('woff'),
+ url('~res/font/ffz-fontello.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+[class^="ffz-i-"]:before, [class*=" ffz-i-"]:before {
+ font-family: "ffz-fontello";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+
+ display: inline-block;
+ text-decoration: inherit;
+ width: 1em;
+ margin-right: .2em;
+ text-align: center;
+ /* opacity: .8; */
+
+ /* For safety - reset parent styles, that can break glyph codes*/
+ font-variant: normal;
+ text-transform: none;
+
+ /* fix buttons height, for twitter bootstrap */
+ line-height: 1em;
+
+ /* Animation center compensation - margins should be symmetric */
+ /* remove if not needed */
+ margin-left: .2em;
+
+ /* you can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+
+ /* Font smoothing. That was taken from TWBS */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ /* Uncomment for 3D effect */
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
+}
+
+
+.tw-button-icon__icon {
+ [class^="ffz-i-"], [class*=" ffz-i-"] {
+ font-size: 1.6rem
+ }
+}
+
+
+.ffz-i-zreknarf:before {
+ content: '\e801'; /* '' */
+ width: 1.3em;
+ margin: .5rem .05rem 0;
+}
+
+
+.ffz-i-cancel:before { content: '\e800'; } /* '' */
+.ffz-i-search:before { content: '\e802'; } /* '' */
+.ffz-i-clock:before { content: '\e803'; } /* '' */
+.ffz-i-star:before { content: '\e804'; } /* '' */
+.ffz-i-star-empty:before { content: '\e805'; } /* '' */
+.ffz-i-down-dir:before { content: '\e806'; } /* '' */
+.ffz-i-right-dir:before { content: '\e807'; } /* '' */
+.ffz-i-attention:before { content: '\e808'; } /* '' */
+.ffz-i-ok:before { content: '\e809'; } /* '' */
+.ffz-i-cog:before { content: '\e80a'; } /* '' */
+.ffz-i-plus:before { content: '\e80b'; } /* '' */
+.ffz-i-folder-open:before { content: '\e80c'; } /* '' */
+.ffz-i-download:before { content: '\e80d'; } /* '' */
+.ffz-i-upload:before { content: '\e80e'; } /* '' */
+.ffz-i-floppy:before { content: '\e80f'; } /* '' */
+.ffz-i-twitter:before { content: '\f099'; } /* '' */
+.ffz-i-gauge:before { content: '\f0e4'; } /* '' */
+.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
+.ffz-i-upload-cloud:before { content: '\f0ee'; } /* '' */
+.ffz-i-keyboard:before { content: '\f11c'; } /* '' */
+.ffz-i-ellipsis-vert:before { content: '\f142'; } /* '' */
+.ffz-i-twitch:before { content: '\f1e8'; } /* '' */
+.ffz-i-trash:before { content: '\f1f8'; } /* '' */
+.ffz-i-window-maximize:before { content: '\f2d0'; } /* '' */
+.ffz-i-window-minimize:before { content: '\f2d1'; } /* '' */
+.ffz-i-window-restore:before { content: '\f2d2'; } /* '' */
+.ffz-i-window-close:before { content: '\f2d3'; } /* '' */
+
+
+.ffz-i-pd-1:before { margin-right: 1rem }
+.ffz-i-pd-2:before { margin-right: 2rem }
+.ffz-i-pd-3:before { margin-right: 3rem }
\ No newline at end of file
diff --git a/styles/main.scss b/styles/main.scss
new file mode 100644
index 00000000..155df2d1
--- /dev/null
+++ b/styles/main.scss
@@ -0,0 +1,12 @@
+@import 'icons';
+@import 'tooltips';
+@import 'widgets';
+
+@import 'chat';
+
+@keyframes ffz-rotateplane {
+ 0% { transform: rotateY(90deg) rotateX(180deg) }
+ 25% { transform: rotateY(0deg) rotateX(180deg) }
+ 75% { transform: rotateY(0deg) rotateX(0deg) }
+ 100% { transform: rotateY(90deg) rotateX(0deg) }
+}
\ No newline at end of file
diff --git a/styles/theme.scss b/styles/theme.scss
new file mode 100644
index 00000000..cc91ccd5
--- /dev/null
+++ b/styles/theme.scss
@@ -0,0 +1,2520 @@
+.theme--ffz, .theme--ffz .theme--dark, .theme--ffz.theme--dark {
+ --ffz-color-0: #2b2b2b;
+ --ffz-color-1: #dedede;
+ --ffz-color-2: #d9d9d9;
+ --ffz-color-3: #f0f0f0;
+ --ffz-color-4: #e3e3e3;
+ --ffz-color-5: #a3a3a3;
+ --ffz-color-6: #fff;
+ --ffz-color-7: #bfbfbf;
+ --ffz-color-8: hsla(0, 0%, 100%, 0.05);
+ --ffz-color-9: #949494;
+ --ffz-color-10: #7d7d7d;
+ --ffz-color-11: rgba(163,163,163, 0.2);
+ --ffz-color-12: #f2f2f2;
+ --ffz-color-13: rgba(163,163,163, 0.4);
+ --ffz-color-14: rgba(163,163,163, 0.1);
+ --ffz-color-15: rgba(163,163,163, 0.5);
+ --ffz-color-16: #ec1313;
+ --ffz-color-17: #a50d0d;
+ --ffz-color-18: #616161;
+ --ffz-color-19: rgba(0, 0, 0, 0.25);
+ --ffz-color-20: #14b866;
+ --ffz-color-21: #12a159;
+ --ffz-color-22: #404040;
+ --ffz-color-23: #1f1f1f;
+ --ffz-color-24: hsla(0, 0%, 100%, 0.3);
+ --ffz-color-25: #fafafa;
+ --ffz-color-26: #bfbfbf;
+ --ffz-color-27: rgba(0, 0, 0, 0.6);
+ --ffz-color-28: #5c5c5c;
+ --ffz-color-29: #121212;
+ --ffz-color-30: #d1d1d1;
+ --ffz-color-31: #ffb300;
+ --ffz-color-32: #cccccc;
+ --ffz-color-33: hsla(0, 0%, 100%, 0.15);
+ --ffz-color-34: #0e9bd8;
+ --ffz-color-35: #1c1c1c;
+ --ffz-color-36: #adadad;
+ --ffz-color-37: rgba(0, 0, 0, 0.34);
+ --ffz-color-38: rgba(0, 0, 0, 0.26);
+ --ffz-color-39: rgba(0, 0, 0, 0.28);
+ --ffz-color-40: #121212;
+ --ffz-color-41: #ffea00;
+ --ffz-color-42: rgba(125,125,125, 0.2);
+ --ffz-color-43: rgba(163,163,163, 0.25);
+ --ffz-color-44: rgba(92,92,92, 0.5);
+ --ffz-color-45: rgba(165, 13, 13, 0.2);
+ --ffz-color-46: rgba(236, 19, 19, 0.2);
+ --ffz-color-47: hsla(0, 0%, 100%, 0.8);
+ --ffz-color-48: hsla(0, 0%, 100%, 0.5);
+ --ffz-color-49: rgba(0, 0, 0, 0.05);
+ --ffz-color-50: hsla(0, 0%, 100%, 0.1);
+ --ffz-color-51: #e8e8e8;
+ --ffz-color-52: hsla(0, 0%, 73%, 0.9);
+ --ffz-color-53: #000;
+ --ffz-color-54: #4a4a4a;
+ --ffz-color-55: #3b5998;
+ --ffz-color-56: #45668e;
+ --ffz-color-57: #1da1f2;
+ --ffz-color-58: #ff4500;
+
+ body {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1)
+ }
+ a {
+ color: var(--ffz-color-2)
+ }
+ a:hover {
+ color: var(--ffz-color-3)
+ }
+ a:focus {
+ color: var(--ffz-color-3)
+ }
+ a:active {
+ color: var(--ffz-color-4)
+ }
+ .button {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .button:focus, .button:hover {
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .button:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button:active {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button:disabled {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ border-color: var(--ffz-color-8);
+ box-shadow: none
+ }
+ .button:disabled:focus, .button:disabled:hover {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ border-color: var(--ffz-color-8);
+ box-shadow: none
+ }
+ .button--checkbox, .button--hollow, .button--radio {
+ background-color: transparent;
+ color: var(--ffz-color-1);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .button--checkbox:focus, .button--checkbox:hover, .button--hollow:focus, .button--hollow:hover, .button--radio:focus, .button--radio:hover {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .button--checkbox:focus, .button--hollow:focus, .button--radio:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13)
+ }
+ .button--checkbox:active, .button--hollow:active, .button--radio:active {
+ background-color: var(--ffz-color-14);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-15);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-15);
+ color: var(--ffz-color-12)
+ }
+ .button--checkbox.button--dropmenu:after, .button--hollow.button--dropmenu:after, .button--radio.button--dropmenu:after {
+ border-color: var(--ffz-color-1);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--checkbox.button--dropmenu:focus:after, .button--checkbox.button--dropmenu:hover:after, .button--hollow.button--dropmenu:focus:after, .button--hollow.button--dropmenu:hover:after, .button--radio.button--dropmenu:focus:after, .button--radio.button--dropmenu:hover:after {
+ border-color: var(--ffz-color-12);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--checkbox, .button--radio {
+ background-color: transparent;
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-10);
+ color: var(--ffz-color-1)
+ }
+ .button--checkbox:nth-of-type(n+2), .button--radio:nth-of-type(n+2) {
+ -webkit-box-shadow: inset -1px 0 0 var(--ffz-color-10), inset 0 1px 0 var(--ffz-color-10), inset 0 -1px 0 var(--ffz-color-10);
+ box-shadow: inset -1px 0 0 var(--ffz-color-10), inset 0 1px 0 var(--ffz-color-10), inset 0 -1px 0 var(--ffz-color-10)
+ }
+ .button--checkbox [type=checkbox]:focus + .button__label, .button--checkbox [type=radio]:focus + .button__label, .button--radio [type=checkbox]:focus + .button__label, .button--radio [type=radio]:focus + .button__label {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button--checkbox [type=checkbox]:active + .button__label, .button--checkbox [type=radio]:active + .button__label, .button--radio [type=checkbox]:active + .button__label, .button--radio [type=radio]:active + .button__label {
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button--checkbox [type=checkbox]:checked + .button__label, .button--checkbox [type=radio]:checked + .button__label, .button--radio [type=checkbox]:checked + .button__label, .button--radio [type=radio]:checked + .button__label {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .button--checkbox [type=checkbox]:focus + .button__label, .button--radio [type=checkbox]:focus + .button__label {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button--checkbox [type=checkbox]:active + .button__label, .button--radio [type=checkbox]:active + .button__label {
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .button--checkbox [type=checkbox]:checked:focus + .button__label, .button--radio [type=checkbox]:checked:focus + .button__label {
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .button--checkbox [type=checkbox]:checked:active + .button__label, .button--radio [type=checkbox]:checked:active + .button__label {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .button--text {
+ background-color: transparent;
+ color: var(--ffz-color-1)
+ }
+ .button--text:focus, .button--text:hover {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .button--text:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13)
+ }
+ .button--text:active {
+ background-color: var(--ffz-color-14);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-15);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-15);
+ color: var(--ffz-color-12)
+ }
+ .button--text.button--dropmenu:after {
+ border-color: var(--ffz-color-1);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--text.button--dropmenu:hover:after {
+ border-color: var(--ffz-color-12);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--alert {
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-16)
+ }
+ .button--alert:focus, .button--alert:hover {
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-17)
+ }
+ .button--alert:active {
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-16)
+ }
+ .button--icon figure svg {
+ fill: var(--ffz-color-6)
+ }
+ .button--icon.button--hollow figure svg {
+ fill: var(--ffz-color-1)
+ }
+ .button--icon.button--text figure svg {
+ fill: var(--ffz-color-1)
+ }
+ .button--icon-only {
+ background-color: transparent
+ }
+ .button--icon-only figure svg {
+ fill: var(--ffz-color-1)
+ }
+ .button--icon-only:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-13)
+ }
+ .button--icon-only:focus, .button--icon-only:hover {
+ background-color: var(--ffz-color-11)
+ }
+ .button--icon-only:focus figure svg, .button--icon-only:hover figure svg {
+ fill: var(--ffz-color-12)
+ }
+ .button--icon-only:active {
+ background-color: var(--ffz-color-14)
+ }
+ .button--icon-only:active figure svg {
+ fill: var(--ffz-color-12)
+ }
+ .button--icon-only.button--secondary figure svg {
+ fill: var(--ffz-color-9)
+ }
+ .button--icon-only.button--secondary:focus figure svg, .button--icon-only.button--secondary:hover figure svg {
+ fill: var(--ffz-color-18)
+ }
+ .button--icon-only.button--secondary:active figure svg {
+ fill: var(--ffz-color-18)
+ }
+ .button--icon-only.button--dropmenu:after {
+ border-color: var(--ffz-color-1);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--icon-only.button--dropmenu:hover:after {
+ border-color: var(--ffz-color-12);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button--dropmenu:after {
+ border-color: var(--ffz-color-6);
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: transparent
+ }
+ .button__num-block {
+ background-color: var(--ffz-color-19)
+ }
+ .button--status {
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-20)
+ }
+ .button--status.button--icon.button--icon-only figure svg {
+ fill: var(--ffz-color-6)
+ }
+ .button--status:focus, .button--status:hover {
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-16)
+ }
+ .button--status:active {
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-16)
+ }
+ .button--success {
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-20)
+ }
+ .button--success:focus, .button--success:hover {
+ background-color: var(--ffz-color-21);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-20)
+ }
+ .button--success:active {
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6);
+ border-color: var(--ffz-color-20)
+ }
+ .button--disabled {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ border-color: var(--ffz-color-8)
+ }
+ .button--disabled:focus, .button--disabled:hover {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ border-color: var(--ffz-color-8)
+ }
+ .card a:hover {
+ color: var(--ffz-color-1)
+ }
+ .card--bordered {
+ background-color: var(--ffz-color-0);
+ border-color: var(--ffz-color-22)
+ }
+ .card__img {
+ background-color: var(--ffz-color-23)
+ }
+ .card__img--avatar:after {
+ border-color: var(--ffz-color-24)
+ }
+ .card__body--alt {
+ background-color: var(--ffz-color-23)
+ }
+ .card__overlay .card__bread, .card__overlay .card__title {
+ color: var(--ffz-color-25)
+ }
+ .card__overlay .card__info {
+ color: var(--ffz-color-26)
+ }
+ a:focus .card__overlay .card__title, a:hover .card__overlay .card__title {
+ color: var(--ffz-color-25)
+ }
+ .card__bread {
+ color: var(--ffz-color-9)
+ }
+ .card__bread a {
+ color: var(--ffz-color-9)
+ }
+ .card__title {
+ color: var(--ffz-color-1)
+ }
+ .card__title a {
+ color: var(--ffz-color-1)
+ }
+ .card__title a:hover {
+ color: var(--ffz-color-2)
+ }
+ a:hover .card__title {
+ color: var(--ffz-color-2)
+ }
+ .card__info {
+ color: var(--ffz-color-9)
+ }
+ .card__info a {
+ color: var(--ffz-color-9)
+ }
+ .card__info a:hover {
+ color: var(--ffz-color-2)
+ }
+ .card__meta {
+ background-color: var(--ffz-color-27);
+ color: var(--ffz-color-25)
+ }
+ .card__meta svg {
+ fill: var(--ffz-color-25)
+ }
+ .card__delete svg {
+ fill: var(--ffz-color-26)
+ }
+ .form__icon svg {
+ fill: var(--ffz-color-25)
+ }
+ .form__input[type=email], .form__input[type=password], .form__input[type=search], .form__input[type=text], select.form__input, textarea.form__input {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-25)
+ }
+ .form__input[type=email]::-webkit-input-placeholder, .form__input[type=password]::-webkit-input-placeholder, .form__input[type=search]::-webkit-input-placeholder, .form__input[type=text]::-webkit-input-placeholder, select.form__input::-webkit-input-placeholder, textarea.form__input::-webkit-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .form__input[type=email]::-moz-placeholder, .form__input[type=password]::-moz-placeholder, .form__input[type=search]::-moz-placeholder, .form__input[type=text]::-moz-placeholder, select.form__input::-moz-placeholder, textarea.form__input::-moz-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .form__input[type=email]:-ms-input-placeholder, .form__input[type=password]:-ms-input-placeholder, .form__input[type=search]:-ms-input-placeholder, .form__input[type=text]:-ms-input-placeholder, select.form__input:-ms-input-placeholder, textarea.form__input:-ms-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .form__input[type=email]::placeholder, .form__input[type=password]::placeholder, .form__input[type=search]::placeholder, .form__input[type=text]::placeholder, select.form__input::placeholder, textarea.form__input::placeholder {
+ color: var(--ffz-color-9)
+ }
+ .form__input[type=email]:focus, .form__input[type=password]:focus, .form__input[type=search]:focus, .form__input[type=text]:focus, select.form__input:focus, textarea.form__input:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .form__input[type=checkbox], .form__input[type=radio] {
+ color: var(--ffz-color-25)
+ }
+ .form__input[type=checkbox] + label, .form__input[type=radio] + label {
+ color: var(--ffz-color-25)
+ }
+ .form__input[type=checkbox] + label:before, .form__input[type=radio] + label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ background-color: var(--ffz-color-29)
+ }
+ .form__input[type=checkbox]:focus + label:before, .form__input[type=radio]:focus + label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .form__input[type=checkbox]:checked + label:before {
+ background-color: var(--ffz-color-7);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .form__input[type=checkbox]:checked + label:after {
+ border-bottom-color: var(--ffz-color-6);
+ border-left-color: var(--ffz-color-6)
+ }
+ .form__input[type=checkbox]:checked:checked:focus + label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .form__input[type=radio] + label:after {
+ background-color: var(--ffz-color-29)
+ }
+ .form__input[type=radio]:checked + label:after {
+ background-color: var(--ffz-color-7)
+ }
+ .form__label {
+ color: var(--ffz-color-25)
+ }
+ .form__label--optional:after {
+ color: var(--ffz-color-9)
+ }
+ .form__hint {
+ color: var(--ffz-color-9)
+ }
+ .form__group--error .form__label {
+ color: var(--ffz-color-16)
+ }
+ .form__group--error .form__input, .form__group--error .form__input:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .form__group--error .form__hint--error {
+ color: var(--ffz-color-16)
+ }
+ .form__input[type=range]::-moz-range-track {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-ms-track {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-webkit-slider-runnable-track {
+ background-color: var(--ffz-color-28);
+ -webkit-box-shadow: var(--ffz-color-28) 0 0 0 1px inset;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-moz-range-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-ms-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-webkit-slider-thumb {
+ -webkit-box-shadow: var(--ffz-color-28) 0 0 0 1px inset;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-ms-fill-lower {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]::-ms-fill-upper {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .form__input[type=range]:focus::-moz-range-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .form__input[type=range]:focus::-ms-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .form__input[type=range]:focus::-webkit-slider-thumb {
+ -webkit-box-shadow: var(--ffz-color-7) 0 0 0 1px inset, 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-tabs {
+ -webkit-box-shadow: inset 0 -1px 0 var(--ffz-color-28);
+ box-shadow: inset 0 -1px 0 var(--ffz-color-28)
+ }
+ .tw-tabs__item > a, .tw-tabs__item > button {
+ color: var(--ffz-color-30)
+ }
+ .tw-tabs__item > a:hover, .tw-tabs__item > button:hover {
+ -webkit-box-shadow: 0 -1px 0 var(--ffz-color-5) inset;
+ box-shadow: inset 0 -1px 0 var(--ffz-color-5)
+ }
+ .tw-tabs__item > a:focus, .tw-tabs__item > button:focus {
+ -webkit-box-shadow: 0 -2px 0 var(--ffz-color-5) inset, 0 4px 6px -4px var(--ffz-color-7);
+ box-shadow: inset 0 -2px 0 var(--ffz-color-5), 0 4px 6px -4px var(--ffz-color-7)
+ }
+ .tw-tabs__item > a.active, .tw-tabs__item > button.active {
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: 0 -1px 0 var(--ffz-color-5) inset;
+ box-shadow: inset 0 -1px 0 var(--ffz-color-5)
+ }
+ .brick {
+ background-color: var(--ffz-color-0);
+ border-color: var(--ffz-color-22)
+ }
+ .balloon-wrapper--hotspot:after {
+ background-color: var(--ffz-color-7)
+ }
+ .balloon-wrapper--hotspot:hover:after {
+ background-color: var(--ffz-color-7)
+ }
+ .balloon {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1);
+ -webkit-box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19);
+ box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19)
+ }
+ .balloon:after {
+ background-color: var(--ffz-color-0)
+ }
+ .balloon--left:after {
+ -webkit-box-shadow: 1px -1px 0 var(--ffz-color-22);
+ box-shadow: 1px -1px 0 var(--ffz-color-22)
+ }
+ .balloon--right:after {
+ -webkit-box-shadow: -1px 1px 0 var(--ffz-color-22);
+ box-shadow: -1px 1px 0 var(--ffz-color-22)
+ }
+ .balloon--up:after {
+ -webkit-box-shadow: 1px 1px 0 var(--ffz-color-22);
+ box-shadow: 1px 1px 0 var(--ffz-color-22)
+ }
+ .balloon--down:after {
+ -webkit-box-shadow: -1px -1px 0 var(--ffz-color-22);
+ box-shadow: -1px -1px 0 var(--ffz-color-22)
+ }
+ .balloon--tooltip {
+ background-color: var(--ffz-color-6);
+ color: var(--ffz-color-29);
+ box-shadow: none
+ }
+ .balloon--tooltip:after {
+ background-color: var(--ffz-color-6);
+ box-shadow: none
+ }
+ .balloon--cols .balloon__list ~ .balloon__list {
+ -webkit-box-shadow: -1px 0 0 var(--ffz-color-22);
+ box-shadow: -1px 0 0 var(--ffz-color-22)
+ }
+ .balloon .balloon__link {
+ color: var(--ffz-color-2)
+ }
+ .balloon .balloon__link.balloon__link--selected {
+ background-color: var(--ffz-color-28) !important
+ }
+ .balloon .balloon__link.balloon__link--active, .balloon .balloon__link:hover {
+ background-color: var(--ffz-color-10);
+ color: var(--ffz-color-6)
+ }
+ .balloon__link.balloon__link--alert {
+ color: var(--ffz-color-16)
+ }
+ .balloon__link.balloon__link--disabled {
+ color: var(--ffz-color-9)
+ }
+ .balloon__title {
+ color: var(--ffz-color-9)
+ }
+ .balloon__stroke {
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .balloon--alert {
+ background-color: var(--ffz-color-16)
+ }
+ .balloon--alert {
+ color: var(--ffz-color-6) !important
+ }
+ .balloon--alert:after {
+ background-color: var(--ffz-color-16)
+ }
+ .balloon--alert:after {
+ color: var(--ffz-color-6)
+ }
+ .pill {
+ background-color: var(--ffz-color-28);
+ color: var(--ffz-color-6)
+ }
+ .progress-bar {
+ background-color: var(--ffz-color-10)
+ }
+ .progress-bar__fill {
+ background-color: var(--ffz-color-20)
+ }
+ .progress-bar--error {
+ background-color: var(--ffz-color-16)
+ }
+ .progress-bar--caution {
+ background-color: var(--ffz-color-31)
+ }
+ .progress-bar--countdown .progress-bar__fill {
+ background-color: var(--ffz-color-32)
+ }
+ .toggle input[type=checkbox]:focus + .toggle__button {
+ background-color: var(--ffz-color-10);
+ -webkit-box-shadow: 0 0 6px -2px var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: 0 0 6px -2px var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .toggle input[type=checkbox]:checked + .toggle__button {
+ background-color: var(--ffz-color-20)
+ }
+ .toggle input[type=checkbox]:checked + .toggle__button:before {
+ border-bottom-color: var(--ffz-color-6);
+ border-left-color: var(--ffz-color-6)
+ }
+ .toggle__button {
+ background-color: var(--ffz-color-10)
+ }
+ .toggle__button:hover {
+ background-color: var(--ffz-color-10)
+ }
+ .toggle__button:after {
+ background-color: var(--ffz-color-6)
+ }
+ .toggle__label-off, .toggle__label-on {
+ color: var(--ffz-color-9)
+ }
+ .border {
+ border-color: var(--ffz-color-22)
+ }
+ .border-t {
+ border-top-color: var(--ffz-color-22)
+ }
+ .border-r {
+ border-right-color: var(--ffz-color-22)
+ }
+ .border-b {
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .border-l {
+ border-left-color: var(--ffz-color-22)
+ }
+ .border--marked {
+ border-left-color: var(--ffz-color-5) !important
+ }
+ .c-background-body {
+ background-color: var(--ffz-color-23) !important
+ }
+ .c-background {
+ background-color: var(--ffz-color-0) !important
+ }
+ .c-background-alt {
+ background-color: var(--ffz-color-23) !important
+ }
+ .c-background-alt-2 {
+ background-color: var(--ffz-color-29) !important
+ }
+ .c-background-accent {
+ background-color: var(--ffz-color-5) !important
+ }
+ .c-background-accent-alt {
+ background-color: var(--ffz-color-28) !important
+ }
+ .c-background-accent-alt-2 {
+ background-color: var(--ffz-color-22) !important
+ }
+ .c-background-placeholder {
+ background-color: var(--ffz-color-8) !important
+ }
+ .c-background-overlay-placeholder {
+ background-color: var(--ffz-color-33) !important
+ }
+ .c-background-live {
+ background-color: var(--ffz-color-16) !important
+ }
+ .c-background-prime {
+ background-color: var(--ffz-color-34) !important
+ }
+ .c-background-tooltip {
+ background-color: var(--ffz-color-6) !important
+ }
+ .c-background-overlay {
+ background-color: var(--ffz-color-27) !important
+ }
+ .c-background-top-nav {
+ background-color: var(--ffz-color-28) !important
+ }
+ .c-background-chat {
+ background-color: var(--ffz-color-29) !important
+ }
+ .c-background-chat-alt {
+ background-color: var(--ffz-color-0) !important
+ }
+ .c-background-chat-header {
+ background-color: var(--ffz-color-23) !important
+ }
+ .c-background-chat-line-mentioning {
+ background-color: var(--ffz-color-28) !important
+ }
+ .c-background-chat-line-mentioned {
+ background-color: var(--ffz-color-12) !important
+ }
+ .c-background-modal-overlay {
+ background-color: var(--ffz-color-27) !important
+ }
+ .c-background-modal {
+ background-color: var(--ffz-color-0) !important
+ }
+ .c-background-graph {
+ background-color: var(--ffz-color-23) !important
+ }
+ .c-background-graph-fill {
+ background-color: var(--ffz-color-7) !important
+ }
+ .c-text {
+ color: var(--ffz-color-1) !important
+ }
+ .c-text-alt {
+ color: var(--ffz-color-26) !important
+ }
+ .c-text-alt-2 {
+ color: var(--ffz-color-9) !important
+ }
+ .c-text-link {
+ color: var(--ffz-color-2) !important
+ }
+ .c-text-link-hover {
+ color: var(--ffz-color-3) !important
+ }
+ .c-text-link-focus {
+ color: var(--ffz-color-3) !important
+ }
+ .c-text-link-active {
+ color: var(--ffz-color-4) !important
+ }
+ .c-text-link-visited {
+ color: var(--ffz-color-2) !important
+ }
+ .c-text-alert, .c-text-live {
+ color: var(--ffz-color-16) !important
+ }
+ .c-text-prime {
+ color: var(--ffz-color-34) !important
+ }
+ .c-text-hint {
+ color: var(--ffz-color-9) !important
+ }
+ .c-text-error {
+ color: var(--ffz-color-16) !important
+ }
+ .c-text-tooltip {
+ color: var(--ffz-color-29) !important
+ }
+ .c-text-overlay {
+ color: var(--ffz-color-25) !important
+ }
+ .c-text-overlay-alt {
+ color: var(--ffz-color-26) !important
+ }
+ .c-text-chat-line-mentioning {
+ color: var(--ffz-color-6) !important
+ }
+ .c-text-chat-line-mentioned {
+ color: var(--ffz-color-35) !important
+ }
+ .c-border {
+ border-color: var(--ffz-color-22) !important
+ }
+ .c-border-brand {
+ border-color: var(--ffz-color-5) !important
+ }
+ .c-border-alert, .c-border-error {
+ border-color: var(--ffz-color-16) !important
+ }
+ .c-border-whisper-incoming {
+ border-color: var(--ffz-color-2) !important
+ }
+ .c-border-whisper-outgoing {
+ border-color: var(--ffz-color-36) !important
+ }
+ .c-border-spinner {
+ border-color: var(--ffz-color-33) !important
+ }
+ .c-border-spinner-fill {
+ border-color: var(--ffz-color-1) !important
+ }
+ .elevation-1 {
+ -webkit-box-shadow: 0 2px 4px -1px var(--ffz-color-37), 0 2px 2px -2px var(--ffz-color-38), 0 1px 4px 0 var(--ffz-color-39) !important;
+ box-shadow: 0 2px 4px -1px var(--ffz-color-37), 0 2px 2px -2px var(--ffz-color-38), 0 1px 4px 0 var(--ffz-color-39) !important
+ }
+ .elevation-2 {
+ -webkit-box-shadow: 0 4px 6px -2px var(--ffz-color-37), 0 3px 4px -2px var(--ffz-color-38), 0 1px 8px 0 var(--ffz-color-39) !important;
+ box-shadow: 0 4px 6px -2px var(--ffz-color-37), 0 3px 4px -2px var(--ffz-color-38), 0 1px 8px 0 var(--ffz-color-39) !important
+ }
+ .elevation-3 {
+ -webkit-box-shadow: 0 6px 10px 0 var(--ffz-color-37), 0 1px 8px -3px var(--ffz-color-38), 0 2px 16px 0 var(--ffz-color-39) !important;
+ box-shadow: 0 6px 10px 0 var(--ffz-color-37), 0 1px 8px -3px var(--ffz-color-38), 0 2px 16px 0 var(--ffz-color-39) !important
+ }
+ .elevation-4 {
+ -webkit-box-shadow: 0 10px 14px 4px var(--ffz-color-37), 0 6px 14px -6px var(--ffz-color-38), 0 4px 24px 0 var(--ffz-color-39) !important;
+ box-shadow: 0 10px 14px 4px var(--ffz-color-37), 0 6px 14px -6px var(--ffz-color-38), 0 4px 24px 0 var(--ffz-color-39) !important
+ }
+ .elevation-5 {
+ -webkit-box-shadow: 0 14px 20px 8px var(--ffz-color-37), 0 10px 22px -8px var(--ffz-color-38), 0 8px 38px 0 var(--ffz-color-39) !important;
+ box-shadow: 0 14px 20px 8px var(--ffz-color-37), 0 10px 22px -8px var(--ffz-color-38), 0 8px 38px 0 var(--ffz-color-39) !important
+ }
+ .twilight-root {
+ background-color: var(--ffz-color-23);
+ color: var(--ffz-color-1)
+ }
+ .tw-typeset p code {
+ background-color: var(--ffz-color-29)
+ }
+ .tw-typeset pre {
+ border-color: var(--ffz-color-22);
+ background-color: var(--ffz-color-29)
+ }
+ .tw-typeset blockquote {
+ border-left-color: var(--ffz-color-5)
+ }
+ .tw-typeset hr {
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .tw-tooltip {
+ background-color: var(--ffz-color-6);
+ color: var(--ffz-color-29)
+ }
+ .tw-tooltip:after {
+ background-color: var(--ffz-color-6)
+ }
+ .tw-thumbnail-card a:hover .tw-thumbnail-card__title {
+ color: var(--ffz-color-2)
+ }
+ .tw-thumbnail-card__title {
+ color: var(--ffz-color-1)
+ }
+ .tw-thumbnail-card__meta {
+ color: var(--ffz-color-9)
+ }
+ .tw-textarea {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent
+ }
+ .tw-textarea::-webkit-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-textarea::-moz-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-textarea:-ms-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-textarea::placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-textarea:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-textarea--error {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .tw-tab-nav {
+ -webkit-box-shadow: inset 0 -1px 0 var(--ffz-color-28);
+ box-shadow: inset 0 -1px 0 var(--ffz-color-28)
+ }
+ .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) a, .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) button {
+ color: var(--ffz-color-30)
+ }
+ .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) a:hover, .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) button:hover {
+ -webkit-box-shadow: 0 -1px 0 var(--ffz-color-5) inset;
+ box-shadow: inset 0 -1px 0 var(--ffz-color-5)
+ }
+ .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) a:focus, .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) button:focus {
+ -webkit-box-shadow: 0 -2px 0 var(--ffz-color-5) inset, 0 4px 6px -4px var(--ffz-color-7);
+ box-shadow: inset 0 -2px 0 var(--ffz-color-5), 0 4px 6px -4px var(--ffz-color-7)
+ }
+ .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) a.active, .tw-tab-nav__item:not(.tw-tab-nav__item--disabled) button.active {
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: 0 -1px 0 var(--ffz-color-5) inset;
+ box-shadow: inset 0 -1px 0 var(--ffz-color-5)
+ }
+ .tw-tab-nav__item--disabled a, .tw-tab-nav__item--disabled button {
+ color: var(--ffz-color-9)
+ }
+ .tw-tab-nav__item--disabled a:hover, .tw-tab-nav__item--disabled button:hover {
+ color: var(--ffz-color-9)
+ }
+ .tw-select {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent
+ }
+ .tw-select::-webkit-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-select::-moz-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-select:-ms-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-select::placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-select:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-select--error {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .tw-button--checkbox, .tw-button--radio {
+ border-color: var(--ffz-color-10);
+ background-color: transparent;
+ color: var(--ffz-color-1)
+ }
+ .tw-button--checkbox:hover, .tw-button--radio:hover {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button--checkbox:focus, .tw-button--radio:focus {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox:active, .tw-button--radio:active {
+ border-color: var(--ffz-color-15);
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox [type=checkbox]:focus + .tw-button__label, .tw-button--checkbox [type=radio]:focus + .tw-button__label, .tw-button--radio [type=checkbox]:focus + .tw-button__label, .tw-button--radio [type=radio]:focus + .tw-button__label {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox [type=checkbox]:active + .tw-button__label, .tw-button--checkbox [type=radio]:active + .tw-button__label, .tw-button--radio [type=checkbox]:active + .tw-button__label, .tw-button--radio [type=radio]:active + .tw-button__label {
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox [type=checkbox]:checked + .tw-button__label, .tw-button--checkbox [type=radio]:checked + .tw-button__label, .tw-button--radio [type=checkbox]:checked + .tw-button__label, .tw-button--radio [type=radio]:checked + .tw-button__label {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--checkbox [type=checkbox]:focus + .tw-button__label, .tw-button--radio [type=checkbox]:focus + .tw-button__label {
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox [type=checkbox]:active + .tw-button__label, .tw-button--radio [type=checkbox]:active + .tw-button__label {
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--checkbox [type=checkbox]:checked:focus + .tw-button__label, .tw-button--radio [type=checkbox]:checked:focus + .tw-button__label {
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--checkbox [type=checkbox]:checked:active + .tw-button__label, .tw-button--radio [type=checkbox]:checked:active + .tw-button__label {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-range, .tw-range:active, .tw-range:focus, .tw-range:hover {
+ background-color: transparent;
+ box-shadow: none
+ }
+ .tw-range::-moz-range-track {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-ms-track {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-webkit-slider-runnable-track {
+ background-color: var(--ffz-color-28);
+ -webkit-box-shadow: var(--ffz-color-28) 0 0 0 1px inset;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-moz-range-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-ms-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-webkit-slider-thumb {
+ -webkit-box-shadow: var(--ffz-color-28) 0 0 0 1px inset;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-ms-fill-lower {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range::-ms-fill-upper {
+ background-color: var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .tw-range:focus::-moz-range-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-range:focus::-ms-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-range:focus::-webkit-slider-thumb {
+ -webkit-box-shadow: var(--ffz-color-7) 0 0 0 1px inset, 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-range--error::-moz-range-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .tw-range--error::-ms-thumb {
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .tw-range--error::-webkit-slider-thumb {
+ -webkit-box-shadow: var(--ffz-color-16) 0 0 0 1px inset;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .tw-toggle__input:focus + .tw-toggle__button {
+ background-color: var(--ffz-color-10);
+ -webkit-box-shadow: 0 0 6px -2px var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: 0 0 6px -2px var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .tw-toggle__input:checked + .tw-toggle__button {
+ background-color: var(--ffz-color-20)
+ }
+ .tw-toggle__input:checked + .tw-toggle__button:before {
+ border-bottom-color: var(--ffz-color-6);
+ border-left-color: var(--ffz-color-6)
+ }
+ .tw-toggle__button {
+ background-color: var(--ffz-color-10)
+ }
+ .tw-toggle__button:hover {
+ background-color: var(--ffz-color-10)
+ }
+ .tw-toggle__button:after {
+ background-color: var(--ffz-color-6)
+ }
+ .tw-toggle--error .tw-toggle__button {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent
+ }
+ .tw-radio__input {
+ color: var(--ffz-color-25)
+ }
+ .tw-radio__input:checked + .tw-radio__label:after {
+ background-color: var(--ffz-color-7)
+ }
+ .tw-radio__input:focus + .tw-radio__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-radio__label {
+ color: var(--ffz-color-25)
+ }
+ .tw-radio__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent
+ }
+ .tw-radio__label:before {
+ background-color: var(--ffz-color-29)
+ }
+ .tw-radio__label:after {
+ background-color: var(--ffz-color-29)
+ }
+ .tw-radio--error .tw-radio__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent
+ }
+ .tw-progress-bar {
+ background-color: var(--ffz-color-10)
+ }
+ .tw-progress-bar.tw-progress-bar--vod {
+ background-color: var(--ffz-color-40)
+ }
+ .tw-progress-bar.tw-progress-bar--vod .tw-progress-bar__fill {
+ background-color: var(--ffz-color-2)
+ }
+ .tw-progress-bar__fill {
+ background-color: var(--ffz-color-20)
+ }
+ .tw-progress-bar--error {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-progress-bar--caution {
+ background-color: var(--ffz-color-31)
+ }
+ .tw-progress-bar--countdown .tw-progress-bar__fill {
+ background-color: var(--ffz-color-32)
+ }
+ .tw-placeholder:before {
+ background-color: var(--ffz-color-8)
+ }
+ .tw-placeholder--overlay:before {
+ background-color: var(--ffz-color-33)
+ }
+ .tw-pill {
+ background-color: var(--ffz-color-28);
+ color: var(--ffz-color-6)
+ }
+ .tw-pill--alt {
+ background-color: var(--ffz-color-30)
+ }
+ .tw-pill--alt2 {
+ background-color: var(--ffz-color-9)
+ }
+ .tw-pill--brand {
+ background-color: var(--ffz-color-2)
+ }
+ .tw-pill--live, .tw-pill--notification {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-pill--warn {
+ background-color: var(--ffz-color-41)
+ }
+ .tw-pill--alert {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-pill--success {
+ background-color: var(--ffz-color-20)
+ }
+ .tw-pill--prime {
+ background-color: var(--ffz-color-34)
+ }
+ .tw-tag {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-tag {
+ border-color: transparent
+ }
+ .tw-tag:hover {
+ border-color: transparent;
+ background-color: var(--ffz-color-7)
+ }
+ .tw-tag:focus {
+ border-color: var(--ffz-color-32);
+ background-color: var(--ffz-color-7);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-tag:active {
+ border-color: var(--ffz-color-7);
+ background-color: var(--ffz-color-5);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-tag:disabled, .tw-tag:disabled:active, .tw-tag:disabled:focus {
+ border-color: var(--ffz-color-8);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ box-shadow: none
+ }
+ .tw-tag__close:after {
+ border-left-color: var(--ffz-color-22)
+ }
+ .tw-loading-spinner {
+ border-top-color: var(--ffz-color-33);
+ border-right-color: var(--ffz-color-33);
+ border-bottom-color: var(--ffz-color-33);
+ border-left-color: var(--ffz-color-1)
+ }
+ .tw-loading-spinner--inherit-color {
+ border-top-color: var(--ffz-color-33);
+ border-right-color: var(--ffz-color-33);
+ border-bottom-color: var(--ffz-color-33)
+ }
+ .tw-live-indicator {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-live-indicator--pulse:after {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-group__label {
+ color: var(--ffz-color-25)
+ }
+ .tw-group__optional {
+ color: var(--ffz-color-9)
+ }
+ .tw-group__hint {
+ color: var(--ffz-color-9)
+ }
+ .tw-group--error .tw-group__hint, .tw-group--error .tw-group__label, .tw-group__hint--error {
+ color: var(--ffz-color-16)
+ }
+ .tw-interactable--disabled, .tw-interactable:disabled {
+ background-color: var(--ffz-color-8)
+ }
+ .tw-interactable--hover, .tw-interactable:hover {
+ background-color: var(--ffz-color-10);
+ color: var(--ffz-color-6)
+ }
+ .tw-interactable:active {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-interactable:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .tw-interactable--selected {
+ background-color: var(--ffz-color-28)
+ }
+ .tw-interactable--alpha.tw-interactable--hover, .tw-interactable--alpha:hover {
+ background-color: var(--ffz-color-42)
+ }
+ .tw-interactable--alpha:active {
+ background-color: var(--ffz-color-43)
+ }
+ .tw-interactable--alpha:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .tw-interactable--alpha.tw-interactable--selected {
+ background-color: var(--ffz-color-44)
+ }
+ .tw-interactable--alert {
+ color: var(--ffz-color-16)
+ }
+ .tw-interactable--alert--hover, .tw-interactable--alert:focus, .tw-interactable--alert:hover {
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .tw-interactable--alert--active, .tw-interactable--alert:active {
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-interactable--alert.tw-interactable--alpha {
+ color: var(--ffz-color-16)
+ }
+ .tw-interactable--alert.tw-interactable--alpha.tw-interactable--hover, .tw-interactable--alert.tw-interactable--alpha:focus, .tw-interactable--alert.tw-interactable--alpha:hover {
+ background-color: var(--ffz-color-45)
+ }
+ .tw-interactable--alert.tw-interactable--alpha--active, .tw-interactable--alert.tw-interactable--alpha:active {
+ background-color: var(--ffz-color-46)
+ }
+ .tw-input__icon svg {
+ fill: var(--ffz-color-25)
+ }
+ .tw-input {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent
+ }
+ .tw-input::-webkit-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-input::-moz-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-input:-ms-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-input::placeholder {
+ color: var(--ffz-color-9)
+ }
+ .tw-input:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-input--error {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16)
+ }
+ .svg {
+ fill: var(--ffz-color-25)
+ }
+ .svg--alt {
+ fill: var(--ffz-color-30)
+ }
+ .svg--alt-2 {
+ fill: var(--ffz-color-9)
+ }
+ .svg--brand {
+ fill: var(--ffz-color-2)
+ }
+ .svg--live {
+ fill: var(--ffz-color-16)
+ }
+ .svg--warn {
+ fill: var(--ffz-color-41)
+ }
+ .svg--alert {
+ fill: var(--ffz-color-16)
+ }
+ .svg--success {
+ fill: var(--ffz-color-20)
+ }
+ .svg--prime {
+ fill: var(--ffz-color-34)
+ }
+ .svg--placeholder {
+ fill: var(--ffz-color-8)
+ }
+ .svg--overlay-placeholder {
+ fill: var(--ffz-color-33)
+ }
+ .svg--inherit {
+ fill: currentColor;
+ }
+ .tw-drop-zone {
+ border-color: var(--ffz-color-22)
+ }
+ .tw-drop-zone--error {
+ border-color: var(--ffz-color-16)
+ }
+ .tw-drop-zone--over {
+ border-color: var(--ffz-color-5)
+ }
+ .tw-checkbox__input {
+ color: var(--ffz-color-25)
+ }
+ .tw-checkbox__input:checked + .tw-checkbox__label:before {
+ background-color: var(--ffz-color-7);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .tw-checkbox__input:checked + .tw-checkbox__label:after {
+ border-bottom-color: var(--ffz-color-6);
+ border-left-color: var(--ffz-color-6)
+ }
+ .tw-checkbox__input:focus + .tw-checkbox__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .tw-checkbox__label {
+ color: var(--ffz-color-25)
+ }
+ .tw-checkbox__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28), 0 0 0 transparent
+ }
+ .tw-checkbox--error .tw-checkbox__label:before {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent;
+ box-shadow: inset 0 0 0 1px var(--ffz-color-16), 0 0 0 transparent
+ }
+ .tw-balloon {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1);
+ -webkit-box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19);
+ box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19)
+ }
+ .tw-balloon > .tw-balloon__tail {
+ background-color: var(--ffz-color-0)
+ }
+ .tw-balloon--left > .tw-balloon__tail {
+ -webkit-box-shadow: 1px -1px 0 var(--ffz-color-22);
+ box-shadow: 1px -1px 0 var(--ffz-color-22)
+ }
+ .tw-balloon--right > .tw-balloon__tail {
+ -webkit-box-shadow: -1px 1px 0 var(--ffz-color-22);
+ box-shadow: -1px 1px 0 var(--ffz-color-22)
+ }
+ .tw-balloon--up > .tw-balloon__tail {
+ -webkit-box-shadow: 1px 1px 0 var(--ffz-color-22);
+ box-shadow: 1px 1px 0 var(--ffz-color-22)
+ }
+ .tw-balloon--down > .tw-balloon__tail {
+ -webkit-box-shadow: -1px -1px 0 var(--ffz-color-22);
+ box-shadow: -1px -1px 0 var(--ffz-color-22)
+ }
+ .tw-button-icon {
+ border-color: transparent;
+ background-color: transparent
+ }
+ .tw-button-icon {
+ color: var(--ffz-color-1)
+ }
+ .tw-button-icon:hover {
+ border-color: transparent;
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button-icon:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button-icon:focus {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button-icon:active {
+ border-color: var(--ffz-color-15);
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12)
+ }
+ .tw-button-icon--disabled, .tw-button-icon:disabled {
+ border-color: var(--ffz-color-8);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9)
+ }
+ .tw-button-icon--disabled:focus, .tw-button-icon--disabled:hover, .tw-button-icon:disabled:focus, .tw-button-icon:disabled:hover {
+ border-color: var(--ffz-color-8);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ box-shadow: none
+ }
+ .tw-button-icon--primary {
+ border-color: transparent;
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--primary:hover {
+ border-color: var(--ffz-color-7);
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--primary:focus {
+ border-color: var(--ffz-color-32);
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button-icon--primary:active {
+ border-color: var(--ffz-color-7);
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button-icon--hollow {
+ border-color: var(--ffz-color-10);
+ background-color: transparent;
+ color: var(--ffz-color-1)
+ }
+ .tw-button-icon--hollow:hover {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button-icon--hollow:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button-icon--hollow:focus {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button-icon--hollow:active {
+ border-color: var(--ffz-color-15);
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button-icon--alert {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--alert:focus, .tw-button-icon--alert:hover {
+ border-color: var(--ffz-color-17);
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--alert:active {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--success {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--success:focus, .tw-button-icon--success:hover {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-21);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--success:active {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--overlay {
+ border-color: transparent;
+ background-color: transparent;
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--overlay:hover {
+ border-color: var(--ffz-color-6);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--overlay:focus {
+ border-color: var(--ffz-color-6)
+ }
+ .tw-button-icon--overlay:focus {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-47);
+ box-shadow: 0 0 6px 0 var(--ffz-color-47)
+ }
+ .tw-button-icon--overlay:active {
+ border-color: var(--ffz-color-6);
+ background-color: transparent;
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-47);
+ box-shadow: 0 0 6px 0 var(--ffz-color-47)
+ }
+ .tw-button-icon--overlay.tw-button-icon--hollow {
+ border-color: var(--ffz-color-6)
+ }
+ .tw-button-icon--overlay.tw-button-icon--disabled, .tw-button-icon--overlay.tw-button-icon--disabled:focus, .tw-button-icon--overlay.tw-button-icon--disabled:hover, .tw-button-icon--overlay:disabled, .tw-button-icon--overlay:disabled:focus, .tw-button-icon--overlay:disabled:hover {
+ background-color: transparent;
+ color: var(--ffz-color-48)
+ }
+ .tw-button-icon--status:focus, .tw-button-icon--status:hover {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--status:active {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button-icon--secondary {
+ color: var(--ffz-color-9)
+ }
+ .tw-button-icon--secondary:hover {
+ color: var(--ffz-color-18)
+ }
+ .tw-button-icon--secondary:focus {
+ color: var(--ffz-color-18)
+ }
+ .tw-button-icon--secondary:active {
+ color: var(--ffz-color-18)
+ }
+ .tw-button {
+ border-color: transparent;
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .tw-button:hover {
+ border-color: var(--ffz-color-7);
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .tw-button:focus {
+ border-color: var(--ffz-color-32);
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button:active {
+ border-color: var(--ffz-color-7);
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--hollow, .tw-button--hollow.tw-button--icon-only {
+ border-color: var(--ffz-color-10);
+ background-color: transparent;
+ color: var(--ffz-color-1)
+ }
+ .tw-button--hollow.tw-button--icon-only:hover, .tw-button--hollow:hover {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button--hollow.tw-button--icon-only:focus, .tw-button--hollow:focus {
+ border-color: var(--ffz-color-13);
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--hollow.tw-button--icon-only:active, .tw-button--hollow:active {
+ border-color: var(--ffz-color-15);
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--alert {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--alert:focus, .tw-button--alert:hover {
+ border-color: var(--ffz-color-17);
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--alert:active {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--success {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--success:focus, .tw-button--success:hover {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-21);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--success:active {
+ border-color: var(--ffz-color-20);
+ background-color: var(--ffz-color-20);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--status:focus, .tw-button--status:hover {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--status:active {
+ border-color: var(--ffz-color-16);
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--text {
+ background-color: transparent;
+ color: var(--ffz-color-1)
+ }
+ .tw-button--text:focus, .tw-button--text:hover {
+ border-color: transparent;
+ background-color: var(--ffz-color-11);
+ color: var(--ffz-color-12)
+ }
+ .tw-button--text:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--text:focus {
+ border-color: var(--ffz-color-13)
+ }
+ .tw-button--text:active {
+ border-color: var(--ffz-color-15);
+ background-color: var(--ffz-color-14);
+ color: var(--ffz-color-12);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .tw-button--overlay {
+ border-color: transparent;
+ background-color: transparent;
+ color: var(--ffz-color-6)
+ }
+ .tw-button--overlay:hover {
+ border-color: var(--ffz-color-6);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-6)
+ }
+ .tw-button--overlay:focus {
+ border-color: var(--ffz-color-6)
+ }
+ .tw-button--overlay:focus {
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-47);
+ box-shadow: 0 0 6px 0 var(--ffz-color-47)
+ }
+ .tw-button--overlay:active {
+ border-color: var(--ffz-color-6);
+ background-color: transparent;
+ color: var(--ffz-color-6);
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-47);
+ box-shadow: 0 0 6px 0 var(--ffz-color-47)
+ }
+ .tw-button--overlay.tw-button--hollow, .tw-button--overlay.tw-button--state-loading, .tw-button--overlay.tw-button--state-success {
+ border-color: var(--ffz-color-6)
+ }
+ .tw-button--overlay.tw-button--hollow.tw-button--disabled, .tw-button--overlay.tw-button--hollow.tw-button--disabled:active, .tw-button--overlay.tw-button--hollow.tw-button--disabled:focus, .tw-button--overlay.tw-button--hollow.tw-button--disabled:hover, .tw-button--overlay.tw-button--state-loading.tw-button--disabled, .tw-button--overlay.tw-button--state-loading.tw-button--disabled:active, .tw-button--overlay.tw-button--state-loading.tw-button--disabled:focus, .tw-button--overlay.tw-button--state-loading.tw-button--disabled:hover, .tw-button--overlay.tw-button--state-success.tw-button--disabled, .tw-button--overlay.tw-button--state-success.tw-button--disabled:active, .tw-button--overlay.tw-button--state-success.tw-button--disabled:focus, .tw-button--overlay.tw-button--state-success.tw-button--disabled:hover {
+ border-color: var(--ffz-color-48)
+ }
+ .tw-button--overlay.tw-button--disabled, .tw-button--overlay.tw-button--disabled:focus, .tw-button--overlay.tw-button--disabled:hover {
+ background-color: transparent;
+ color: var(--ffz-color-48)
+ }
+ .tw-button--disabled {
+ border-color: var(--ffz-color-8);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9)
+ }
+ .tw-button--disabled:active, .tw-button--disabled:focus, .tw-button--disabled:hover {
+ border-color: var(--ffz-color-8);
+ background-color: var(--ffz-color-8);
+ color: var(--ffz-color-9);
+ box-shadow: none
+ }
+ .tw-button__num-block {
+ background-color: var(--ffz-color-19)
+ }
+ .tw-box-art-card a:hover .tw-box-art-card__title {
+ color: var(--ffz-color-2)
+ }
+ .tw-box-art-card__title {
+ color: var(--ffz-color-1)
+ }
+ .tw-box-art-card__meta {
+ color: var(--ffz-color-9)
+ }
+ .tw-badge {
+ background-color: var(--ffz-color-2);
+ color: var(--ffz-color-25)
+ }
+ .tw-badge--alt {
+ background-color: var(--ffz-color-30)
+ }
+ .tw-badge--alt2 {
+ background-color: var(--ffz-color-9)
+ }
+ .tw-badge--brand {
+ background-color: var(--ffz-color-2)
+ }
+ .tw-badge--live, .tw-badge--notification {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-badge--warn {
+ background-color: var(--ffz-color-41)
+ }
+ .tw-badge--alert {
+ background-color: var(--ffz-color-16)
+ }
+ .tw-badge--success {
+ background-color: var(--ffz-color-20)
+ }
+ .tw-badge--prime {
+ background-color: var(--ffz-color-34)
+ }
+ .tw-badge--dashboard {
+ background-color: var(--ffz-color-28)
+ }
+ .thread-header__title-bar-container {
+ background-color: var(--ffz-color-23);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-22), 0 2px 0 var(--ffz-color-49);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-22), 0 2px 0 var(--ffz-color-49)
+ }
+ .thread-header__title-bar-container--focused {
+ background-color: var(--ffz-color-29)
+ }
+ .thread-header__title-bar-container--highlighted {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .emote-picker__content-block:not(:first-child) {
+ border-top-color: var(--ffz-color-22)
+ }
+ .emote-picker__tab {
+ color: var(--ffz-color-2)
+ }
+ .emote-picker__tab:hover {
+ border-top-color: var(--ffz-color-5)
+ }
+ .emote-picker__tab--active {
+ color: var(--ffz-color-25);
+ border-top-color: var(--ffz-color-5)
+ }
+ .emote-picker__tab--disabled {
+ color: var(--ffz-color-9)
+ }
+ .emote-picker__emote-link:hover {
+ background-color: var(--ffz-color-50)
+ }
+ .emote-picker__emote-lock {
+ background-color: var(--ffz-color-27);
+ color: var(--ffz-color-25)
+ }
+ .whispers-list-item:hover {
+ background-color: var(--ffz-color-29)
+ }
+ .side-nav__overlay-wrapper, .side-nav__theme-wrapper {
+ background-color: var(--ffz-color-23)
+ }
+ .side-nav__toggle-visibility {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1)
+ }
+ .side-nav-card__link {
+ background-color: var(--ffz-color-23)
+ }
+ .side-nav-card__title {
+ color: var(--ffz-color-26)
+ }
+ .side-nav-card__metadata {
+ color: var(--ffz-color-9)
+ }
+ .side-nav-card:hover .side-nav-card__title {
+ color: var(--ffz-color-1)
+ }
+ .side-nav-card:hover .side-nav-card__metadata {
+ color: var(--ffz-color-26)
+ }
+ .follow-btn__status-btn {
+ background-color: var(--ffz-color-20);
+ border-color: var(--ffz-color-20)
+ }
+ .follow-btn__status-btn .svg--heart, .follow-btn__status-btn .svg--unheart {
+ fill: var(--ffz-color-6)
+ }
+ .follow-btn__status-btn:hover {
+ background-color: var(--ffz-color-16);
+ border-color: var(--ffz-color-20)
+ }
+ .follow-btn__dropdown-toggle {
+ background-color: var(--ffz-color-51);
+ color: var(--ffz-color-9)
+ }
+ .followed-channels__load-more {
+ color: var(--ffz-color-9)
+ }
+ .followed-channels__load-more:hover {
+ color: var(--ffz-color-26);
+ background-color: var(--ffz-color-23)
+ }
+ .followed-channel__live-indicator, .followed-channel__live-indicator--watch-party {
+ background-color: var(--ffz-color-16)
+ }
+ .followed-channel__live-indicator--watch-party {
+ background-color: var(--ffz-color-9)
+ }
+ .right-column__toggle-visibility {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1)
+ }
+ .user-card__overlay {
+ background-color: var(--ffz-color-27)
+ }
+ .user-card__status-btn {
+ background-color: var(--ffz-color-20);
+ border-color: var(--ffz-color-20)
+ }
+ .user-card__status-btn:hover {
+ background-color: var(--ffz-color-16);
+ border-color: var(--ffz-color-20)
+ }
+ .top-nav__home-link, .top-nav__home-link:hover {
+ color: var(--ffz-color-6)
+ }
+ .top-nav__menu {
+ background-color: var(--ffz-color-28)
+ }
+ .top-nav__nav-link.active, .top-nav__nav-link:hover {
+ color: var(--ffz-color-6)
+ }
+ .top-nav__beta-badge {
+ background-color: var(--ffz-color-16);
+ color: var(--ffz-color-6)
+ }
+ .top-nav__beta-badge:hover {
+ background-color: var(--ffz-color-17);
+ color: var(--ffz-color-6)
+ }
+ .blue-bar {
+ background-color: var(--ffz-color-34)
+ }
+ .blue-bar__link {
+ color: var(--ffz-color-1)
+ }
+ .blue-bar__link:hover {
+ color: var(--ffz-color-3)
+ }
+ .search-panel__highlight-link {
+ color: var(--ffz-color-2)
+ }
+ .search-panel__link {
+ color: var(--ffz-color-9)
+ }
+ .search-panel__card:hover .search-panel__highlight-link, .search-panel__card:hover .search-panel__link {
+ color: var(--ffz-color-6)
+ }
+ .notice-wrap__title {
+ color: var(--ffz-color-9)
+ }
+ .notice-wrap__text {
+ color: var(--ffz-color-9)
+ }
+ .search-title-bar {
+ background-color: var(--ffz-color-29);
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .search-result-view__titlesep {
+ color: var(--ffz-color-9);
+ background-color: var(--ffz-color-23)
+ }
+ .search-result-view__titlesep:hover {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-2)
+ }
+ .search-live-result-card__info {
+ color: var(--ffz-color-26)
+ }
+ .onsite-notification-toast {
+ -webkit-box-shadow: 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: 0 0 6px -2px var(--ffz-color-7)
+ }
+ .onsite-notification-toast--hover, .onsite-notification-toast:hover {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7)
+ }
+ .persistent-notification__read {
+ background-color: var(--ffz-color-0)
+ }
+ .persistent-notification__unread {
+ background-color: var(--ffz-color-29)
+ }
+ .language-selector__list--border-right {
+ border-right-color: var(--ffz-color-22)
+ }
+ .dashboard-nav__link {
+ color: var(--ffz-color-2)
+ }
+ .dashboard-nav__link--active {
+ background-color: var(--ffz-color-28)
+ }
+ .dashboard-nav__link:hover {
+ background-color: var(--ffz-color-10);
+ color: var(--ffz-color-6)
+ }
+ .dashboard-nav__link:active {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .dashboard-hotspot--announcement {
+ background-color: var(--ffz-color-16)
+ }
+ .twp-button:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .twp-button--hollow {
+ color: var(--ffz-color-2)
+ }
+ .twp-button--hollow {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .twp-button--hollow:focus, .twp-button--hollow:hover {
+ background-color: var(--ffz-color-11)
+ }
+ .twp-button--hollow:focus, .twp-button--hollow:hover {
+ color: var(--ffz-color-2)
+ }
+ .twp-button--hollow:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .twp-button--text {
+ color: var(--ffz-color-2)
+ }
+ .twp-button--text:focus, .twp-button--text:hover {
+ background-color: var(--ffz-color-11)
+ }
+ .twp-button--text:focus, .twp-button--text:hover {
+ color: var(--ffz-color-2)
+ }
+ .twp-button--text:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .twp-button--icon-only figure svg {
+ fill: var(--ffz-color-2)
+ }
+ .twp-button--icon-only:focus {
+ -webkit-box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10);
+ box-shadow: 0 0 6px 0 var(--ffz-color-7), inset 0 0 0 1px var(--ffz-color-10)
+ }
+ .twp-button--icon-only:focus figure svg, .twp-button--icon-only:hover figure svg {
+ fill: var(--ffz-color-3)
+ }
+ .twp-card__title {
+ color: var(--ffz-color-1)
+ }
+ .twp-card__title a {
+ color: var(--ffz-color-1)
+ }
+ .twp-card__title a:hover {
+ color: var(--ffz-color-2)
+ }
+ a:hover .twp-card__title {
+ color: var(--ffz-color-2)
+ }
+ .twp-card__info {
+ color: var(--ffz-color-26)
+ }
+ .twp-card__info a {
+ color: var(--ffz-color-26)
+ }
+ .twp-card__info a:hover {
+ color: var(--ffz-color-2)
+ }
+ .phx-selectbox {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-25);
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-28);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-28)
+ }
+ .phx-selectbox::-webkit-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .phx-selectbox::-moz-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .phx-selectbox:-ms-input-placeholder {
+ color: var(--ffz-color-9)
+ }
+ .phx-selectbox::placeholder {
+ color: var(--ffz-color-9)
+ }
+ .phx-selectbox:focus {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7), 0 0 6px -2px var(--ffz-color-7)
+ }
+ .vod-message.vod-message--focused {
+ -webkit-box-shadow: inset 0 0 0 1px var(--ffz-color-7);
+ box-shadow: inset 0 0 0 1px var(--ffz-color-7)
+ }
+ .vod-message.vod-message--focused, .vod-message:hover {
+ background-color: var(--ffz-color-22)
+ }
+ .vod-message.vod-message--focused .vod-message__timestamp, .vod-message:hover .vod-message__timestamp {
+ background-color: var(--ffz-color-5)
+ }
+ .vod-message.vod-message--focused .vod-message__timestamp, .vod-message:hover .vod-message__timestamp {
+ color: var(--ffz-color-6)
+ }
+ .vod-message.vod-message--focused .vod-message__timestamp:hover, .vod-message:hover .vod-message__timestamp:hover {
+ background-color: var(--ffz-color-7)
+ }
+ .vod-message.vod-message--focused .vod-message__reply, .vod-message:hover .vod-message__reply {
+ -webkit-box-shadow: inset 3px 0 0 0 var(--ffz-color-18);
+ box-shadow: inset 3px 0 0 0 var(--ffz-color-18)
+ }
+ .video-chat__message-list-wrapper::-webkit-scrollbar-thumb {
+ border-color: var(--ffz-color-23)
+ }
+ .video-chat__sync-button {
+ background-color: var(--ffz-color-52)
+ }
+ .video-chat__sync-button {
+ color: var(--ffz-color-53)
+ }
+ .video-chat__sync-button:focus, .video-chat__sync-button:hover {
+ background-color: var(--ffz-color-26)
+ }
+ .video-chat__sync-button:focus, .video-chat__sync-button:hover {
+ color: var(--ffz-color-53)
+ }
+ .vod-message__timestamp {
+ color: var(--ffz-color-9)
+ }
+ .video-chat__message-author {
+ color: var(--ffz-color-2)
+ }
+ .video-chat__message-author--creator, .video-chat__message-author--creator:hover, .video-chat__message-author--me, .video-chat__message-author--me:hover {
+ color: var(--ffz-color-6)
+ }
+ .vod-message__show-more-replies {
+ color: var(--ffz-color-2)
+ }
+ .vod-message__reply {
+ -webkit-box-shadow: inset 3px 0 0 0 var(--ffz-color-54);
+ box-shadow: inset 3px 0 0 0 var(--ffz-color-54)
+ }
+ .video-chat {
+ background-color: var(--ffz-color-29)
+ }
+ .video-chat__input {
+ -webkit-box-shadow: inset 0 1px 0 0 var(--ffz-color-22);
+ box-shadow: inset 0 1px 0 0 var(--ffz-color-22)
+ }
+ .video-chat__header {
+ -webkit-box-shadow: inset 0 -1px 0 0 var(--ffz-color-22);
+ box-shadow: inset 0 -1px 0 0 var(--ffz-color-22)
+ }
+ .mod-comment__unpublished-message {
+ background-color: var(--ffz-color-28)
+ }
+ .sm-sb-link:focus .sm-sb, .sm-sb-link:hover .sm-sb {
+ background-color: var(--ffz-color-28)
+ }
+ .sm-sb-link:focus .sm-sb__text, .sm-sb-link:hover .sm-sb__text {
+ background-color: var(--ffz-color-28)
+ }
+ .sm-sb {
+ background-color: var(--ffz-color-5)
+ }
+ .sm-sb__text {
+ background-color: var(--ffz-color-5)
+ }
+ .sm-cp-card__title {
+ color: var(--ffz-color-1)
+ }
+ .sm-cp-card a:hover .sm-cp-card__title {
+ color: var(--ffz-color-2)
+ }
+ .sm-graph-panel {
+ background-color: var(--ffz-color-5)
+ }
+ .footer__link, .footer__link:hover {
+ color: var(--ffz-color-6)
+ }
+ .live-channel-card__meta {
+ color: var(--ffz-color-9)
+ }
+ .live-channel-card__channel {
+ color: var(--ffz-color-1)
+ }
+ .live-channel-card__videos {
+ color: var(--ffz-color-9)
+ }
+ .live-channel-card__channel:hover, .live-channel-card__videos:hover {
+ color: var(--ffz-color-2)
+ }
+ .live-channel-card__boxart {
+ border-color: var(--ffz-color-0)
+ }
+ .carousel-nav__item {
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .carousel-nav__item--active {
+ border-bottom-color: var(--ffz-color-5)
+ }
+ .channel-header__subscription-balloon-options .subscription-balloon-options__prime-crown {
+ color: var(--ffz-color-34)
+ }
+ .channel-header__subscription-balloon-options .subscription-balloon-options__subbed-star {
+ color: var(--ffz-color-20)
+ }
+ .anon-front__social-container {
+ background-color: var(--ffz-color-0)
+ }
+ .twi-drop-zone {
+ border-color: var(--ffz-color-22)
+ }
+ .twi-drop-zone--over {
+ border-color: var(--ffz-color-5)
+ }
+ .social-button__link--copy:after {
+ color: var(--ffz-color-5)
+ }
+ .social-button__link--facebook:after {
+ color: var(--ffz-color-55)
+ }
+ .social-button__link--vkontakte:after {
+ color: var(--ffz-color-56)
+ }
+ .social-button__link--twitter:after {
+ color: var(--ffz-color-57)
+ }
+ .social-button__link--reddit:after {
+ color: var(--ffz-color-58)
+ }
+ .social-button__icon--reddit {
+ background-color: var(--ffz-color-58)
+ }
+ .social-button__icon--twitter {
+ background-color: var(--ffz-color-57)
+ }
+ .social-button__icon--vkontakte {
+ background-color: var(--ffz-color-56)
+ }
+ .social-button__icon--facebook {
+ background-color: var(--ffz-color-55)
+ }
+ .social-button__icon--copy {
+ background-color: var(--ffz-color-5)
+ }
+ .t-bits-card__footer {
+ background-color: var(--ffz-color-0)
+ }
+ .t-bits-card__footer {
+ border-top-color: var(--ffz-color-22)
+ }
+ .t-bits-card__footer {
+ color: var(--ffz-color-9)
+ }
+ .t-bits-card__top-controls {
+ color: var(--ffz-color-9)
+ }
+ .bits-buy-card__sub-text {
+ color: var(--ffz-color-9)
+ }
+ .bits-buy-card__close {
+ color: var(--ffz-color-9)
+ }
+ .cheermote-list .cheermote-list__item-button {
+ border-color: var(--ffz-color-22)
+ }
+ .cheermote-list .cheermote-list__item-button[disabled] {
+ background-color: var(--ffz-color-28)
+ }
+ .cheermote-list .cheermote-list__item-button:focus:not([disabled]), .cheermote-list .cheermote-list__item-button:hover:not([disabled]) {
+ background-color: var(--ffz-color-28);
+ -webkit-box-shadow: 0 1px 5px var(--ffz-color-28);
+ box-shadow: 0 1px 5px var(--ffz-color-28)
+ }
+ .pinned-cheer__bounding-box {
+ background-color: var(--ffz-color-0);
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .pinned-cheer__headline {
+ color: var(--ffz-color-2)
+ }
+ .pinned-cheer__progress-bar {
+ background-color: var(--ffz-color-0)
+ }
+ .channel-header {
+ background-color: var(--ffz-color-0);
+ border-bottom-color: var(--ffz-color-22)
+ }
+ .channel-header__item {
+ color: var(--ffz-color-1)
+ }
+ .channel-header__item:before {
+ background-color: var(--ffz-color-22)
+ }
+ .channel-header__item--selected, .channel-header__item:hover {
+ color: var(--ffz-color-3)
+ }
+ .channel-header__item--selected:after, .channel-header__item:hover:after {
+ border-bottom-color: var(--ffz-color-28)
+ }
+ .channel-header__item--selected:after, .channel-header__item--selected:hover:after, .channel-header__item:active:after {
+ border-bottom-color: var(--ffz-color-5)
+ }
+ .channel-header__item-count {
+ color: var(--ffz-color-9)
+ }
+ .channel-header__dropdown-hover-target:hover .channel-header__item-count {
+ color: var(--ffz-color-26)
+ }
+ .channel-header .interactable:hover .channel-header__item-count {
+ color: var(--ffz-color-26)
+ }
+ .channel-header__user {
+ color: var(--ffz-color-1)
+ }
+ .channel-header__user:hover {
+ color: var(--ffz-color-3)
+ }
+ .channel-header__user:hover .channel-header__user-avatar:after {
+ border-bottom-color: var(--ffz-color-28)
+ }
+ .channel-header__user--selected .channel-header__user-avatar:after, .channel-header__user--selected:hover .channel-header__user-avatar:after, .channel-header__user:active .channel-header__user-avatar:after {
+ border-bottom-color: var(--ffz-color-5)
+ }
+ .channel-header__verified {
+ color: var(--ffz-color-2)
+ }
+ .autocomplete-balloon__item {
+ color: var(--ffz-color-2)
+ }
+ .autocomplete-balloon__item--selected {
+ background-color: var(--ffz-color-7);
+ color: var(--ffz-color-6)
+ }
+ .chat-line__message--mention-sender {
+ background-color: var(--ffz-color-10)
+ }
+ .chat-line__message--mention-recipient {
+ background-color: var(--ffz-color-30)
+ }
+ .chat-line__message--mention-recipient {
+ color: var(--ffz-color-29)
+ }
+ .chat-line__message--special {
+ background-color: var(--ffz-color-29)
+ }
+ .chat-line__moderation, .chat-line__status {
+ color: var(--ffz-color-9)
+ }
+ .chat-line__mod-icons .mod-icon {
+ color: var(--ffz-color-9)
+ }
+ .chat-line__raid, .chat-line__subscribe {
+ color: var(--ffz-color-9)
+ }
+ .chat-line__raid--prime, .chat-line__subscribe--prime {
+ color: var(--ffz-color-2)
+ }
+ .chat-line__raid--message, .chat-line__subscribe--message {
+ color: var(--ffz-color-26)
+ }
+ .chat-line__timestamp {
+ color: var(--ffz-color-9)
+ }
+ .chat-list .chat-list__more-messages {
+ color: var(--ffz-color-25);
+ background-color: var(--ffz-color-27)
+ }
+ .chat__pane {
+ background-color: var(--ffz-color-29);
+ border-left-color: var(--ffz-color-22);
+ color: var(--ffz-color-1)
+ }
+ .chat__header {
+ -webkit-box-shadow: inset 0 -1px 0 0 var(--ffz-color-22);
+ box-shadow: inset 0 -1px 0 0 var(--ffz-color-22);
+ background-color: var(--ffz-color-23)
+ }
+ .chat-settings__swatch--selected {
+ -webkit-box-shadow: 0 0 0 3px var(--ffz-color-28);
+ box-shadow: 0 0 0 3px var(--ffz-color-28)
+ }
+ .chat-settings__swatch:hover {
+ -webkit-box-shadow: 0 0 0 3px var(--ffz-color-10);
+ box-shadow: 0 0 0 3px var(--ffz-color-10)
+ }
+ .chat-settings__badge-chooser--selected {
+ background-color: var(--ffz-color-28)
+ }
+ .chat-settings__badge-chooser:hover {
+ background-color: var(--ffz-color-10)
+ }
+ .chat-settings__badge-chooser__none {
+ color: var(--ffz-color-9);
+ background-color: var(--ffz-color-29)
+ }
+ .chat-viewers__pane {
+ background-color: var(--ffz-color-29);
+ color: var(--ffz-color-26)
+ }
+ .chat-viewers__header {
+ border-bottom-color: var(--ffz-color-22);
+ background-color: var(--ffz-color-23)
+ }
+ .clips-chat-card {
+ background-color: var(--ffz-color-0)
+ }
+ .clips-chat-card:hover .clips-chat-card__title {
+ color: var(--ffz-color-3)
+ }
+ .clips-chat-card__thumb {
+ background-color: var(--ffz-color-29)
+ }
+ .clips-chat-card__title {
+ color: var(--ffz-color-1)
+ }
+ .chat-hosting-notification__container {
+ color: var(--ffz-color-25)
+ }
+ .raid-prompt {
+ color: var(--ffz-color-25)
+ }
+ .chat-resub-notification__notification-wrapper {
+ color: var(--ffz-color-25)
+ }
+ .viewer-card__display-name__link {
+ color: var(--ffz-color-6)
+ }
+ .viewer-card__display-name__link:hover {
+ color: var(--ffz-color-6)
+ }
+ .clip-carousel-button {
+ background-color: var(--ffz-color-5)
+ }
+ .clip-carousel-button .svg--angleleft, .clip-carousel-button .svg--angleright {
+ fill: var(--ffz-color-6)
+ }
+ .fuel-bar {
+ background-color: var(--ffz-color-0)
+ }
+ .channel-info-bar {
+ background-color: var(--ffz-color-0)
+ }
+ .channel-info-bar__action-container {
+ border-top-color: var(--ffz-color-22)
+ }
+ .clmgr-table__row-expanded {
+ border-color: var(--ffz-color-5)
+ }
+ .clmgr-result__subtitle {
+ color: var(--ffz-color-9)
+ }
+ .clmgr-result:hover .clmgr-result__subtitle {
+ color: var(--ffz-color-6)
+ }
+ .clmgr-table__row {
+ background-color: var(--ffz-color-0)
+ }
+ .clmgr-table__row:nth-child(2n) {
+ background-color: var(--ffz-color-23)
+ }
+ .clmgr-table__row:hover {
+ background-color: var(--ffz-color-29)
+ }
+ .event {
+ background-color: var(--ffz-color-0)
+ }
+ .event__header {
+ color: var(--ffz-color-25)
+ }
+ .event__header-overlay {
+ background-color: var(--ffz-color-27)
+ }
+ .event__video:before {
+ background-color: var(--ffz-color-22)
+ }
+ .event__broadcast-wrapper:hover .event__broadcast {
+ border-left-color: var(--ffz-color-10)
+ }
+ .event__broadcast-wrapper:hover .event__broadcast:before {
+ background-color: var(--ffz-color-10)
+ }
+ .event__broadcast {
+ border-left-color: var(--ffz-color-22)
+ }
+ .event__broadcast-details {
+ color: var(--ffz-color-9)
+ }
+ .event__broadcast:before {
+ background-color: var(--ffz-color-22)
+ }
+ .video-preview-card__channel, .video-preview-card__videos {
+ color: var(--ffz-color-1)
+ }
+ .video-preview-card__channel:hover, .video-preview-card__videos:hover {
+ color: var(--ffz-color-2)
+ }
+ .video-preview-card__videos {
+ color: var(--ffz-color-9)
+ }
+ .video-preview-card__boxart--overlay {
+ border-color: var(--ffz-color-0)
+ }
+ .video-carousel:hover .vod-carousel__button {
+ background-color: var(--ffz-color-0)
+ }
+ .video-filters--active {
+ background-color: var(--ffz-color-28)
+ }
+ .video-filters:hover {
+ background-color: var(--ffz-color-10);
+ color: var(--ffz-color-6)
+ }
+ .video-filters:active {
+ background-color: var(--ffz-color-5);
+ color: var(--ffz-color-6)
+ }
+ .follow-game-card--overlay {
+ background-color: var(--ffz-color-27);
+ color: var(--ffz-color-25)
+ }
+ .follow-game-card__meta {
+ color: var(--ffz-color-9)
+ }
+ .embed-card__overlay {
+ background-color: var(--ffz-color-27);
+ color: var(--ffz-color-25)
+ }
+ .embed-card__overlay:hover svg {
+ color: var(--ffz-color-26)
+ }
+ .thumbnail-selector-cropper__thumbnail-select-image:hover:before {
+ background-color: var(--ffz-color-22)
+ }
+ .thumbnail-selector-cropper__thumbnail-select-image--selected {
+ border-color: var(--ffz-color-5)
+ }
+ .video-card-dropdown__dropdown-wrapper {
+ background-color: var(--ffz-color-0);
+ color: var(--ffz-color-1);
+ -webkit-box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19);
+ box-shadow: 0 0 0 1px var(--ffz-color-22), 0 1px 1px var(--ffz-color-19)
+ }
+ .video-card {
+ background-color: var(--ffz-color-0)
+ }
+}
\ No newline at end of file
diff --git a/styles/tooltips.scss b/styles/tooltips.scss
new file mode 100644
index 00000000..138754ff
--- /dev/null
+++ b/styles/tooltips.scss
@@ -0,0 +1,401 @@
+body {
+ overflow-x: hidden
+}
+
+.ffz__tooltip {
+ z-index: 999999999;
+ margin: 6px;
+ border-radius: 2px;
+ background: #0e0c13;
+ color: #fff;
+ font-size: 1.2rem;
+ line-height: 1;
+ text-align: left;
+ pointer-events: none;
+ white-space: pre-wrap;
+
+ &.html { white-space: normal }
+ &.interactive { pointer-events: all }
+
+
+ .loader {
+ opacity: .5;
+ margin: 1rem;
+ font-size: 3rem;
+ animation: ffz-rotateplane 1.2s infinite linear;
+ }
+
+
+ .ffz__tooltip--arrow {
+ position: absolute;
+ width: 6px; height: 6px;
+ transform: rotate(45deg);
+ z-index: -1;
+
+ background: #0e0c13;
+ }
+
+ &[x-placement^="bottom"] {
+ .ffz__tooltip--arrow {
+ top: -3px;
+ border-radius: 2px 0 0;
+ }
+ }
+
+ &[x-placement^="top"] {
+ .ffz__tooltip--arrow {
+ bottom: -3px;
+ border-radius: 0 0 2px;
+ }
+ }
+
+ &[x-placement^="left"] {
+ .ffz__tooltip--arrow {
+ right: -3px;
+ border-radius: 0 2px 0 0;
+ }
+ }
+
+ &[x-placement^="right"] {
+ .ffz__tooltip--arrow {
+ left: -3px;
+ border-radius: 0 0 0 2px;
+ }
+ }
+
+
+ .theme--dark & {
+ background: #fff;
+ color: #0e0c13;
+
+ .ffz__tooltip--arrow {
+ background: #fff;
+ }
+ }
+}
+
+
+.ffz__tooltip--inner {
+ display: block;
+ max-width: 30rem;
+ padding: 3px 6px;
+ text-align: center;
+
+ .preview-image {
+ display: block;
+ margin: 3px auto 6px;
+ }
+
+ br {
+ display: block;
+ }
+
+ .ffz-cheer {
+ margin: 5px;
+ }
+}
+
+
+.ffz-rich-tip {
+ max-width: 340px;
+ width: 340px;
+ text-align: left;
+ position: relative;
+ padding: 8px;
+
+ /*.stats:after, .heading:after {
+ content: '';
+ display: table;
+ clear: both;
+ }
+
+ .tweet-heading {
+ &:before {
+ content: '';
+ position: absolute;
+ top: 24px;
+ right: 24px;
+ width: 20px;
+ height: 16px;
+ background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px;
+ }
+
+ .avatar {
+ border-radius: 50%;
+ }
+ }
+
+ .heading {
+ .title {
+ font-weight: bold;
+ display: block;
+ text-align: justify;
+ }
+ }
+
+ .display-name {
+ padding-top: 3px;
+ font-size: 14px;
+ display: block;
+
+ &.big-name {
+ font-size: 20px;
+ padding-top: 18px;
+ }
+ }
+
+ .quoted {
+ .display-name {
+ padding: 0 5px 0 0;
+ display: inline;
+ font-size: 12px;
+ }
+ }
+
+ .avatar {
+ float: left;
+ margin-right: 8px;
+ max-height: 48px;
+ max-width: 100px;
+ }*/
+}
+
+.ffz-rich-tip .body { line-height: 1.5em }
+
+.ffz-rich-tip .tweet-heading:before {
+ content: '';
+ position: absolute;
+ top: 24px;
+ right: 24px;
+ width: 20px;
+ height: 16px;
+ background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px;
+}
+
+.ffz-rich-tip .stats:after,
+.ffz-rich-tip .heading:after {
+ content: '';
+ display: table;
+ clear: both;
+}
+
+.ffz-rich-tip .avatar {
+ float: left;
+ margin-right: 8px;
+ max-height: 48px;
+ max-width: 100px;
+}
+
+.ffz-rich-tip .tweet-heading .avatar {
+ border-radius: 50%;
+}
+
+.ffz-rich-tip .heading .title {
+ font-weight: bold;
+ display: block;
+ text-align: justify;
+}
+
+.ffz-rich-tip .display-name {
+ padding-top: 3px;
+ font-size: 14px;
+ display: block;
+}
+
+.ffz-rich-tip .display-name.big-name {
+ font-size: 20px;
+ padding-top: 18px;
+}
+
+.ffz-rich-tip .quoted .display-name {
+ padding: 0 5px 0 0;
+ display: inline;
+ font-size: 12px;
+}
+
+.ffz-rich-tip .twitch-heading .tip-badge.verified {
+ height: 12px;
+ width: 12px;
+ margin: 2px 0 -1px 5px;
+ background: url("https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1");
+ background-size: contain;
+ display: inline-block;
+}
+
+.ffz-rich-tip .twitch-heading .big-name .tip-badge.verified {
+ height: 18px;
+ width: 18px;
+ margin: 1px 0 0 10px;
+}
+
+.ffz-rich-tip .tweet-heading .tip-badge {
+ height: 12px;
+ width: 12px;
+ margin: 2px 0 -1px 5px;
+ background: url("//cdn.frankerfacez.com/script/twitter_sprites.png");
+ display: inline-block;
+}
+
+.ffz-rich-tip .tip-badge.verified {
+ background-position: 0 -15px;
+}
+
+.ffz-rich-tip .tip-badge.translator {
+ background-position: -12px -15px;
+}
+
+.ffz-rich-tip .tip-badge.protected {
+ background-position: -24px -15px;
+ width: 14px;
+}
+
+.ffz-rich-tip .emoji {
+ height: 1em;
+ vertical-align: middle;
+}
+
+.ffz-rich-tip .quoted > div:not(:first-child),
+.ffz-rich-tip > div:not(:first-child) {
+ margin-top: 8px;
+}
+
+.ffz-rich-tip .quote-heading + .body,
+.ffz-rich-tip .replying + .body {
+ margin-top: 0 !important;
+}
+
+.ffz-rich-tip .replying {
+ opacity: 0.6;
+ font-size: 10px;
+}
+
+.ffz-rich-tip .subtitle,
+.ffz-rich-tip .quoted .body,
+.ffz-rich-tip .stats,
+.ffz-rich-tip .username { opacity: 0.6 }
+
+.ffz-rich-tip .stats { display: flex }
+.ffz-rich-tip .stats .wide-stat,
+.ffz-rich-tip time { flex-grow: 1 }
+
+.ffz-rich-tip .tweet-stats .stat:before {
+ content: '';
+ display: inline-block;
+ background: url("//cdn.frankerfacez.com/script/twitter_sprites.png");
+ margin: 0 5px 0 10px;
+}
+
+.ffz-rich-tip .stat.likes:before {
+ width: 17px; height: 14px;
+ margin-bottom: -3px;
+}
+
+.ffz-rich-tip .stat.retweets:before {
+ width: 20px; height: 12px;
+ background-position: -34px 0;
+ margin-bottom: -2px;
+}
+
+.ffz-rich-tip .media { position: relative; text-align: center }
+
+.ffz-rich-tip .media[data-count]:not([data-count="1"]) {
+ display: flex;
+ margin: 8px -5px -5px 0;
+ flex-flow: column wrap;
+ height: 329px;
+}
+
+.ffz-rich-tip .media .duration {
+ position: absolute;
+ bottom: 0; right: 0;
+ margin: 4px;
+ background: #000;
+ color: #fff;
+ opacity: .8;
+ padding: 2px 4px;
+ border-radius: 2px;
+ z-index: 10;
+ font-weight: 500;
+}
+
+.ffz-rich-tip .sixteen-nine {
+ width: 100%;
+ overflow: hidden;
+ margin: 0;
+ padding-top: 56.25%;
+ position: relative;
+}
+
+.ffz-rich-tip .sixteen-nine img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ transform: translate(-50%, -50%);
+}
+
+.ffz-rich-tip .media video {
+ width: 100%;
+ max-height: 324px;
+}
+
+.ffz-rich-tip .media[data-count="4"] { flex-flow: row wrap }
+
+.ffz-rich-tip .media[data-count="2"] { height: 164.5px }
+.ffz-rich-tip .media img { max-height: 324px }
+
+.ffz-rich-tip .quoted .media[data-count]:not([data-count="1"]) { height: 150px }
+.ffz-rich-tip .quoted .media[data-count="2"] { height: 150px }
+.ffz-rich-tip .quoted .media video,
+.ffz-rich-tip .quoted .media img { max-height: 150px }
+
+.ffz-rich-tip .media span {
+ background: no-repeat center center;
+ background-size: cover;
+}
+
+.ffz-rich-tip .media[data-count="2"] span,
+.ffz-rich-tip .media[data-count="3"] span,
+.ffz-rich-tip .media[data-count="4"] span {
+ width: calc(50% - 5px);
+ height: calc(50% - 5px);
+ margin: 0 5px 5px 0;
+}
+
+.ffz-rich-tip .media[data-count="2"] span,
+.ffz-rich-tip .media[data-count="3"] span:first-of-type {
+ height: 100%;
+}
+
+.ffz-rich-tip .profile-stats {
+ display: flex;
+ flex-flow: row;
+ flex-wrap: wrap;
+}
+
+.ffz-rich-tip .profile-stats div {
+ flex-grow: 1;
+ font-size: 16px;
+ margin-right: 10px;
+}
+
+.ffz-rich-tip .profile-stats span {
+ display: block;
+ font-size: 12px;
+ opacity: 0.7;
+}
+
+.ffz-rich-tip .quoted {
+ border: 1px solid #474747;
+ border-radius: 5px;
+ padding: 8px;
+}
+
+.ffz-rich-tip .media[data-type="video"]:after {
+ position: absolute;
+ content: '';
+ width: 64px; height: 64px;
+ top: calc(50% - 32px);
+ left: calc(50% - 32px);
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23FFF' d='M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z'/%3E%3C/svg%3E");
+}
\ No newline at end of file
diff --git a/styles/widgets.scss b/styles/widgets.scss
new file mode 100644
index 00000000..a95cf9b6
--- /dev/null
+++ b/styles/widgets.scss
@@ -0,0 +1,162 @@
+@import "./widgets/container.scss";
+
+@import "./widgets/menu-container.scss";
+@import "./widgets/tab-container.scss";
+
+@import "./widgets/menu-tree.scss";
+@import "./widgets/profile-selector.scss";
+
+
+.display-inline { display: inline !important }
+.width-auto { width: auto !important }
+
+.ffz--widget {
+
+ input, select {
+ min-width: 20rem;
+ }
+
+ label {
+ min-width: 15rem;
+ }
+}
+
+
+.ffz--menu-page {
+ padding: 1rem;
+
+ header {
+ a {
+ font-weight: bold;
+ color: inherit;
+ }
+ }
+}
+
+
+.ffz--profile-editor {
+ .tw-button:disabled:not(:hover):not(:focus) {
+ opacity: 0.5;
+ }
+
+ label {
+ width: 10rem;
+ }
+}
+
+
+.ffz--profile-manager {
+ .ffz--profile {
+ outline: none;
+
+ &:focus {
+ box-shadow:
+ inset 0 0 0 1px #7d5bbe,
+ 0 0 6px -2px #7d5bbe !important;
+ }
+
+ .handle {
+ opacity: 0.3;
+ margin: -0.5rem 0;
+ font-size: 2rem;
+ }
+
+ .ffz--profile__icon {
+ font-size: 2rem;
+ }
+
+ .ffz-i-ok { color: green }
+ }
+
+ .sortable-ghost {
+ opacity: 0.25
+ }
+}
+
+
+.ffz--filter-editor {
+ .ffz--rule {
+ outline: none;
+
+ &:focus {
+ box-shadow:
+ inset 0 0 0 1px #7d5bbe,
+ 0 0 6px -2px #7d5bbe !important;
+ }
+
+ .handle {
+ opacity: 0.3;
+ margin: -0.5rem 0;
+ font-size: 2rem;
+ }
+
+ .ffz--filter__icon {
+ font-size: 2rem;
+ }
+
+ .ffz-i-ok { color: green }
+ }
+
+ .sortable-ghost {
+ opacity: 0.25
+ }
+}
+
+
+.ffz--home {
+ h2, p {
+ margin-bottom: 1rem;
+ }
+
+ ul {
+ list-style-type: disc;
+ margin: 1em 0;
+ }
+
+ .tweet-column {
+ width: 300px;
+ }
+}
+
+
+.ffz--changelog {
+ h2, p {
+ margin-bottom: 1rem;
+ }
+
+ .list-header {
+ margin: 10px 20px 5px;
+ padding: 15px 0 5px;
+ border-top: 1px solid #dad8de;
+ font-size: 16px;
+ text-transform: uppercase;
+
+ .theme--dark & {
+ border-top-color: #2c2541;
+ }
+
+ time {
+ opacity: 0.5;
+ }
+ }
+
+ code {
+ padding: 2px 5px;
+ border-radius: 2px;
+ background-color: rgba(0,0,0,0.1);
+
+ .theme--dark & {
+ background-color: rgba(255,255,255,0.05);
+ }
+ }
+
+ .chat-menu-content {
+ margin: 0 20px 20px;
+ }
+
+ #ffz-old-news-button {
+ text-align: center;
+ text-transform: none;
+ padding-bottom: 15px;
+ }
+}
\ No newline at end of file
diff --git a/styles/widgets/container.scss b/styles/widgets/container.scss
new file mode 100644
index 00000000..781a2824
--- /dev/null
+++ b/styles/widgets/container.scss
@@ -0,0 +1,73 @@
+$spacing: 1rem;
+$border-light: #dad8de;
+$border-dark: #2c2541;
+
+$bg-light: #faf9fa;
+$bg-dark: #17141f;
+
+.ffz--outer-container {
+ margin-bottom: $spacing * 2;
+
+ &:last-child {
+ margin-bottom: 0
+ }
+}
+
+
+.ffz--inner-container {
+ padding: $spacing;
+}
+
+.ffz--widget {
+ margin-bottom: $spacing;
+
+ label {
+ padding-top: .5rem;
+ padding-bottom: .5rem;
+ }
+
+ &.default, &.inherits {
+ label:before, label:after {
+ opacity: 0.5;
+ }
+
+ input:focus + label {
+ &:before, &:after {
+ opacity: 1
+ }
+ }
+
+ input, textarea, select {
+ opacity: 0.5;
+
+ &:focus {
+ opacity: 1
+ }
+ }
+ }
+
+ &.inherits {
+ label:before, label:after {
+ filter: grayscale(100%);
+ }
+
+ label:hover,
+ input:focus + label {
+ &:before, &:after {
+ filter: none;
+ }
+ }
+
+ input, textarea, select {
+ filter: grayscale(100%);
+
+ &:hover, &:focus {
+ filter: none;
+ }
+ }
+ }
+
+ &:last-child {
+ margin-bottom: 0
+ }
+}
\ No newline at end of file
diff --git a/styles/widgets/menu-container.scss b/styles/widgets/menu-container.scss
new file mode 100644
index 00000000..29cd79c5
--- /dev/null
+++ b/styles/widgets/menu-container.scss
@@ -0,0 +1,27 @@
+.ffz--menu-container {
+ @extend .ffz--outer-container;
+ @extend .ffz--inner-container;
+
+ position: relative;
+
+ &:not(.border):not(.border-b) {
+ margin-bottom: $spacing;
+ }
+
+ & > header {
+ position: absolute;
+ top: 0; left: $spacing - 0.5rem;
+ transform: translateY(-50%);
+ padding: 0 0.5rem;
+
+ background-color: $bg-light;
+
+ .theme--dark & {
+ background-color: $bg-dark;
+ }
+
+ & + * {
+ margin-top: 0.5rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/styles/widgets/menu-tree.scss b/styles/widgets/menu-tree.scss
new file mode 100644
index 00000000..3e472c79
--- /dev/null
+++ b/styles/widgets/menu-tree.scss
@@ -0,0 +1,69 @@
+.ffz--menu-tree {
+ position: relative;
+
+ &:focus {
+ outline: none;
+
+ &:after {
+ position: absolute;
+ top: 0; left: 0;
+ height: 100%;
+ width: 100%;
+ content: '';
+ pointer-events: none;
+
+ box-shadow: inset 0 0 0 1px #7d5bbe,
+ 0 0 6px -2px #7d5bbe;
+ }
+ }
+
+ .active > div,
+ .active > div:hover,
+ .theme--dark & .active > div:hover {
+ background-color: #6441a4;
+ color: #fff;
+ }
+
+ div:hover {
+ cursor: pointer;
+
+ background-color: #fff;
+
+ .theme--dark & {
+ background-color: #201c2b;
+ }
+ }
+
+ li div {
+ font-size: 1.4rem;
+ margin-top: 0.5rem;
+ padding-left: 0.5rem
+ }
+
+ li:first-child div,
+ li ul div {
+ margin-top: 0rem;
+ }
+
+ li ul div {
+ font-size: inherit;
+ }
+
+ li li div { padding-left: 1rem }
+ li li li div { padding-left: 1.5rem }
+ li li li li div { padding-left: 2rem }
+
+ .pill {
+ font-size: 0.9rem;
+ }
+
+ .arrow {
+ opacity: 0.3;
+ font-size: 1.6rem;
+ }
+}
+
+
+.ffz--invisible {
+ visibility: hidden;
+}
\ No newline at end of file
diff --git a/styles/widgets/profile-selector.scss b/styles/widgets/profile-selector.scss
new file mode 100644
index 00000000..5f8d49da
--- /dev/null
+++ b/styles/widgets/profile-selector.scss
@@ -0,0 +1,64 @@
+.ffz--profile-selector {
+ position: relative;
+
+ .tw-balloon {
+ margin-top: 0 !important
+ }
+
+ .scrollable-area {
+ max-height: 30rem;
+ }
+}
+
+.ffz--profile-row {
+ outline: none;
+ cursor: pointer;
+
+ &.live .ffz--profile-row__icon {
+ color: green;
+ }
+
+ &:not(.live):not(:hover):not(:focus) {
+ opacity: .5;
+ font-variant: italic;
+
+ .theme--dark & {
+ opacity: .25;
+ }
+ }
+
+ .description {
+ opacity: .7;
+ }
+
+ &:first-child {
+ margin-top: 0.5rem;
+ }
+
+ &:last-child {
+ &, .theme--dark & {
+ border-bottom: none !important
+ }
+ }
+
+ &:focus {
+ box-shadow:
+ inset 0 0 0 1px #7d5bbe,
+ 0 0 6px -2px #7d5bbe;
+ }
+
+ &:focus,
+ &:hover {
+ background: rgba(100, 65, 164, .05);
+
+ .theme-dark & {
+ background: rgba(100, 65, 164, .2);
+ }
+ }
+}
+
+.ffz--profile-row__icon {
+ position: absolute;
+ top: 0.5rem; right: 0.5rem;
+ font-size: 1.6rem;
+}
\ No newline at end of file
diff --git a/styles/widgets/tab-container.scss b/styles/widgets/tab-container.scss
new file mode 100644
index 00000000..25877a27
--- /dev/null
+++ b/styles/widgets/tab-container.scss
@@ -0,0 +1,40 @@
+.ffz--tab-container {
+ @extend .ffz--outer-container;
+
+ header:focus {
+ outline: none;
+
+ .tab.active {
+ box-shadow: inset 0 0 0 1px #7d5bbe,
+ 0 0 6px -2px #7d5bbe;
+ }
+ }
+
+ .tab {
+ cursor: pointer;
+ position: relative;
+
+ border-top: 1px solid;
+ border-right: 1px solid;
+ &:first-child { border-left-width: 1px; border-left-style: solid }
+
+ border-color: $border-light;
+
+ .theme--dark & {
+ border-color: $border-dark;
+ }
+
+ &.active:after {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ content: '';
+ border-bottom: 4px solid #6441a4;
+ }
+ }
+
+ & > section {
+ @extend .ffz--inner-container;
+ }
+}
\ No newline at end of file
diff --git a/webpack.common.js b/webpack.common.js
new file mode 100644
index 00000000..c67abf2e
--- /dev/null
+++ b/webpack.common.js
@@ -0,0 +1,66 @@
+const webpack = require('webpack');
+const path = require('path');
+const CleanPlugin = require('clean-webpack-plugin');
+
+module.exports = {
+ entry: {
+ avalon: './src/main.js'
+ },
+ resolve: {
+ alias: {
+ res: path.resolve(__dirname, 'res/'),
+ styles: path.resolve(__dirname, 'styles/'),
+ src: path.resolve(__dirname, 'src/'),
+ utilities: path.resolve(__dirname, 'src/utilities/')
+ }
+ },
+ output: {
+ chunkFilename: '[name].[chunkhash].js',
+ path: path.resolve(__dirname, 'dist'),
+ jsonpFunction: 'ffzWebpackJsonp'
+ },
+ plugins: [
+ new CleanPlugin(['dist']),
+ new webpack.ExtendedAPIPlugin()
+ ],
+ module: {
+ rules: [{
+ test: /\.s?css$/,
+ use: [{
+ loader: 'file-loader',
+ options: {
+ name: '[name].css'
+ }
+ }, {
+ loader: 'extract-loader'
+ }, {
+ loader: 'css-loader',
+ options: {
+ sourceMap: true
+ }
+ }, {
+ loader: 'sass-loader',
+ options: {
+ sourceMap: true
+ }
+ }]
+ },
+ {
+ test: /\.(?:eot|ttf|woff|woff2)$/,
+ use: [{
+ loader: 'file-loader',
+ options: {
+ name: '[name].[ext]'
+ }
+ }]
+ },
+ {
+ test: /\.svg$/,
+ loader: 'raw-loader'
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+ }]
+ }
+}
\ No newline at end of file
diff --git a/webpack.web.common.js b/webpack.web.common.js
new file mode 100644
index 00000000..4608f25c
--- /dev/null
+++ b/webpack.web.common.js
@@ -0,0 +1,11 @@
+const path = require('path');
+const merge = require('webpack-merge');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+ resolve: {
+ alias: {
+ site: path.resolve(__dirname, 'src/sites/twitch-twilight/')
+ }
+ }
+});
\ No newline at end of file
diff --git a/webpack.web.dev.js b/webpack.web.dev.js
new file mode 100644
index 00000000..a3d31789
--- /dev/null
+++ b/webpack.web.dev.js
@@ -0,0 +1,63 @@
+const path = require('path');
+const merge = require('webpack-merge');
+const common = require('./webpack.web.common.js');
+
+const CopyPlugin = require('copy-webpack-plugin');
+const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+const ManifestPlugin = require('webpack-manifest-plugin');
+
+module.exports = merge(common, {
+ devtool: 'inline-source-map',
+
+ plugins: [
+ new CopyPlugin([
+ {
+ from: './src/entry.js',
+ to: 'script.js'
+ }
+ ])
+ ],
+
+ devServer: {
+ https: true,
+ port: 8000,
+ compress: true,
+ inline: false,
+
+ allowedHosts: [
+ '.twitch.tv',
+ '.frankerfacez.com'
+ ],
+
+ contentBase: path.join(__dirname, 'dev_cdn'),
+ publicPath: '/script/',
+
+ proxy: {
+ '**': {
+ target: 'http://cdn.frankerfacez.com/',
+ changeOrigin: true
+ }
+ },
+
+ before(app) {
+ // Because the headers config option is broken.
+ app.get("/*", (req, res, next) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ next();
+ });
+
+ app.get('/dev_server', (req, res) => {
+ res.json({
+ path: process.cwd(),
+ version: 2
+ })
+ });
+ }
+ },
+
+ output: {
+ publicPath: '//localhost:8000/script/',
+ filename: '[name].js',
+ jsonpFunction: 'ffzWebpackJsonp'
+ }
+})
\ No newline at end of file
diff --git a/webpack.web.prod.js b/webpack.web.prod.js
new file mode 100644
index 00000000..82af7c5c
--- /dev/null
+++ b/webpack.web.prod.js
@@ -0,0 +1,68 @@
+const merge = require('webpack-merge');
+const common = require('./webpack.web.common.js');
+
+const CopyPlugin = require('copy-webpack-plugin');
+const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+const ManifestPlugin = require('webpack-manifest-plugin');
+
+const uglify = require('uglify-es');
+
+const config = module.exports = merge(common, {
+ devtool: 'source-map',
+
+ plugins: [
+ new UglifyJSPlugin({
+ sourceMap: true,
+ uglifyOptions: {
+ compress: {
+ keep_fnames: true
+ },
+ mangle: {
+ keep_classnames: true,
+ keep_fnames: true
+ }
+ }
+ }),
+ new CopyPlugin([
+ {
+ from: './src/entry.js',
+ to: 'script.min.js',
+ transform: (content) => {
+ const text = content.toString('utf8');
+ const minified = uglify.minify(text);
+ return (minified && minified.code) ? Buffer.from(minified.code) : content;
+ }
+ }
+ ]),
+ new ManifestPlugin({
+ map: (data) => {
+ if ( data.name.endsWith('.scss') )
+ data.name = data.name.substr(0,data.name.length - 5) + '.css';
+
+ return data;
+ }
+ })
+ ],
+
+ output: {
+ publicPath: '//cdn.frankerfacez.com/script/',
+ filename: '[name].[hash].js'
+ }
+});
+
+
+// This is why we can't have nice things.
+// Why can't I just access process.env.NODE_ENV from
+// one of these files when I set it with webpack's
+// CLI? So stupid.
+//
+// So here we go.
+// This is crap.
+// But it works.
+
+for(const rule of config.module.rules) {
+ if ( rule.use )
+ for(const use of rule.use)
+ if ( use.options && use.options.name && use.options.name.startsWith('[name].') )
+ use.options.name = '[name].[hash].' + use.options.name.slice(7)
+}
\ No newline at end of file