diff --git a/assets/chat/css/abstracts/_index.scss b/assets/chat/css/abstracts/_index.scss new file mode 100644 index 00000000..ea152dc2 --- /dev/null +++ b/assets/chat/css/abstracts/_index.scss @@ -0,0 +1,2 @@ +@forward 'vars'; +@forward 'mixins'; diff --git a/assets/chat/css/abstracts/_mixins.scss b/assets/chat/css/abstracts/_mixins.scss new file mode 100644 index 00000000..3ff8ad0d --- /dev/null +++ b/assets/chat/css/abstracts/_mixins.scss @@ -0,0 +1,24 @@ +@mixin icon-background($url) { + background: transparent url($url) no-repeat center center / contain; +} + +@mixin msg-icon($posX, $posY, $width: 16px, $height: 16px) { + background-image: url('../../icons/icons.png'); + background-repeat: no-repeat; + width: $width; + height: $height; + background-position: $posX $posY; +} + +@mixin slide-in { + animation: msg-slide-in 0.15s cubic-bezier(0.72, 0.03, 0.45, 1); +} + +@keyframes msg-slide-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/assets/common.scss b/assets/chat/css/abstracts/_vars.scss similarity index 82% rename from assets/common.scss rename to assets/chat/css/abstracts/_vars.scss index 8f0995b5..0432800c 100644 --- a/assets/common.scss +++ b/assets/chat/css/abstracts/_vars.scss @@ -1,3 +1,14 @@ +$chat-chrome-font: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; +$chat-lines-font: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; +$chat-min-width: 300px; + +$gutter-xs: 0.2em; +$gutter-sm: 0.3em; +$gutter-md: 0.6em; +$gutter-lg: 0.9em; + +$bradius: 0.25em; + $color-light: #fff7f9; $color-dark: #201f1e; $color-accent: #4a8ecc; diff --git a/assets/chat/css/chat/_autocomplete.scss b/assets/chat/css/chat/_autocomplete.scss new file mode 100644 index 00000000..922b02e0 --- /dev/null +++ b/assets/chat/css/chat/_autocomplete.scss @@ -0,0 +1,65 @@ +@use '../abstracts/' as a; + +#chat-auto-complete { + pointer-events: none; + transition: opacity 750ms; + transition-timing-function: cubic-bezier(0, 0.74, 0.1, 0.99); + opacity: 0; + z-index: 131; + border-radius: a.$bradius a.$bradius 0 0; + position: absolute; + font-size: 1.1em; + line-height: 2em; + height: 2em; + top: -2em; + left: 0; + right: 0; + overflow: hidden; + white-space: nowrap; + + &.active { + opacity: 1; + pointer-events: auto; + } + + ul { + position: absolute; + white-space: nowrap; + list-style: none; + padding: 0; + margin: 0; + } + + li { + padding: 0 a.$gutter-sm; + cursor: pointer; + text-decoration: none; + display: inline-block; + color: a.$color-chat-text3; + background: rgba(a.$color-surface-dark1, 0.75); + + &:first-child { + border-radius: a.$bradius 0 0 0; + } + + &:last-child { + border-radius: 0 a.$bradius 0 0; + } + + &:hover { + color: lighten(a.$color-chat-text3, 20); + } + + &.active { + color: a.$text-color; + } + } +} + +#chat:not(.pref-autocompletehelper) #chat-auto-complete { + display: none; +} + +.pref-autocompletehelper #chat-auto-complete.active { + display: block; +} diff --git a/assets/chat/css/chat/_index.scss b/assets/chat/css/chat/_index.scss new file mode 100644 index 00000000..82467ce2 --- /dev/null +++ b/assets/chat/css/chat/_index.scss @@ -0,0 +1,28 @@ +@use '../abstracts/' as a; + +@use 'autocomplete'; +@use 'input'; +@use 'loading-screen'; +@use 'login-screen'; +@use 'output'; +@use 'scroll-notify'; +@use 'scrollbar-theme'; +@use 'toolbar'; +@use 'window-select'; +@use 'event-bar'; + +#chat { + min-width: a.$chat-min-width; + display: flex; + flex-direction: column; + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} + +.onstreamchat { + #chat { + background: none !important; + } +} diff --git a/assets/chat/css/chat/_input.scss b/assets/chat/css/chat/_input.scss new file mode 100644 index 00000000..b04161c3 --- /dev/null +++ b/assets/chat/css/chat/_input.scss @@ -0,0 +1,51 @@ +@use '../abstracts/' as a; + +#chat-input-frame { + padding: a.$gutter-md a.$gutter-md 0 a.$gutter-md; +} + +#chat-input-wrap { + position: relative; +} + +#chat-input-control { + max-height: 140px; + position: relative; + color: a.$color-chat-text2; + background: a.$color-surface-dark2; + border: 1px solid a.$color-surface-dark3; + outline: none; + resize: none; + margin: 0; + width: 100%; + padding: a.$gutter-md; + border-radius: a.$bradius; + box-shadow: none; + box-sizing: border-box; + display: block; + overflow: hidden; + + &::placeholder { + color: a.$color-chat-place; + } + + #chat.chat-autocomplete-in & { + border-radius: 0 0 a.$bradius a.$bradius; + } +} + +#chat-input-subonly { + cursor: default; + display: none; + position: absolute; + top: a.$gutter-md; + right: a.$gutter-sm; + width: 1.5em; + height: 1.5em; + + .btn-icon { + opacity: 0.75; + + @include a.icon-background('../img/icon-subonly.svg'); + } +} diff --git a/assets/chat/css/chat/_loading-screen.scss b/assets/chat/css/chat/_loading-screen.scss new file mode 100644 index 00000000..68a02ba6 --- /dev/null +++ b/assets/chat/css/chat/_loading-screen.scss @@ -0,0 +1,31 @@ +@use '../abstracts/' as a; + +#chat-loading { + opacity: 0.2; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + .chat-loading-icon { + width: 4em; + height: 4em; + display: inline-block; + animation: spin 2s linear infinite; + + @include a.icon-background('../img/icon-settings.svg'); + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/assets/chat/css/chat/_login-screen.scss b/assets/chat/css/chat/_login-screen.scss new file mode 100644 index 00000000..4f3d188b --- /dev/null +++ b/assets/chat/css/chat/_login-screen.scss @@ -0,0 +1,26 @@ +@use '../abstracts/' as a; + +#chat-login-screen { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: rgba(a.$color-surface-dark1, 0.75); + position: absolute; + text-align: center; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 300; + + h2 { + font-size: 2.5em; + font-weight: normal; + margin: 0; + } + + p { + margin: 1em 0 1.5em 0; + } +} diff --git a/assets/chat/css/chat/_output.scss b/assets/chat/css/chat/_output.scss new file mode 100644 index 00000000..fa2a02a2 --- /dev/null +++ b/assets/chat/css/chat/_output.scss @@ -0,0 +1,33 @@ +@use '../abstracts/' as a; + +#chat-output-frame { + flex: 1; + overflow: hidden; + width: 100%; + position: relative; +} + +.chat-output { + width: 100%; + height: 100%; +} + +.chat-lines { + font-family: a.$chat-lines-font; + line-height: 1.65em; + outline: 0 !important; + overflow-anchor: none; +} + +.onstreamchat { + .chat-lines { + font-size: 1.5em; + font-family: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; + font-weight: 500 !important; + color: a.$text-color; + } + + #chat-output-frame { + margin: 0; + } +} diff --git a/assets/chat/css/chat/_scroll-notify.scss b/assets/chat/css/chat/_scroll-notify.scss new file mode 100644 index 00000000..d4062c2f --- /dev/null +++ b/assets/chat/css/chat/_scroll-notify.scss @@ -0,0 +1,32 @@ +@use '../abstracts/' as a; + +.chat-scroll-notify { + padding: 0.25em 0; + color: a.$color-chat-text3; + background: a.$color-surface-dark3; + position: absolute; + bottom: -2.5em; + left: a.$gutter-md; + right: a.$gutter-md; + text-align: center; + cursor: pointer; + z-index: 130; + border-radius: a.$bradius; + opacity: 0; + transition: all 250ms; + transition-timing-function: cubic-bezier(0.6, 0.08, 0.99, 0.54); + + &:hover { + color: a.$color-chat-text1; + } + + .chat-unpinned & { + transition-timing-function: cubic-bezier(0, 0.99, 0.18, 0.99); + opacity: 1; + bottom: 0; + } + + #chat.chat-autocomplete-in & { + display: none !important; + } +} diff --git a/assets/chat/css/chat/_scrollbar-theme.scss b/assets/chat/css/chat/_scrollbar-theme.scss new file mode 100644 index 00000000..4f8d4d01 --- /dev/null +++ b/assets/chat/css/chat/_scrollbar-theme.scss @@ -0,0 +1,95 @@ +.dgg-scroller-theme.os-scrollbar-horizontal { + right: 10px; + height: 10px; +} + +.dgg-scroller-theme.os-scrollbar-vertical { + bottom: 10px; + width: 10px; +} + +.dgg-scroller-theme.os-scrollbar-rtl.os-scrollbar-horizontal { + left: 10px; + right: 0; +} + +.dgg-scroller-theme.os-scrollbar { + padding: 2px; + box-sizing: border-box; + background: transparent; +} + +.dgg-scroller-theme.os-scrollbar-unusable { + background: transparent; +} + +.dgg-scroller-theme.os-scrollbar > .os-scrollbar-track { + background: transparent; +} + +.dgg-scroller-theme.os-scrollbar-horizontal + > .os-scrollbar-track + > .os-scrollbar-handle { + min-width: 30px; +} + +.dgg-scroller-theme.os-scrollbar-vertical + > .os-scrollbar-track + > .os-scrollbar-handle { + min-height: 30px; +} + +.dgg-scroller-theme.os-scrollbar-transition + > .os-scrollbar-track + > .os-scrollbar-handle { + transition: background-color 0.3s; +} + +.dgg-scroller-theme.os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle { + background: rgba(255, 255, 255, 0.4); +} + +.dgg-scroller-theme.os-scrollbar:hover + > .os-scrollbar-track + > .os-scrollbar-handle { + background: rgba(255, 255, 255, 0.55); +} + +.dgg-scroller-theme.os-scrollbar + > .os-scrollbar-track + > .os-scrollbar-handle.active { + background: rgba(255, 255, 255, 0.7); +} + +.dgg-scroller-theme.os-scrollbar-horizontal .os-scrollbar-handle:before, +.dgg-scroller-theme.os-scrollbar-vertical .os-scrollbar-handle:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: block; +} + +.dgg-scroller-theme.os-scrollbar-horizontal .os-scrollbar-handle:before { + top: -6px; + bottom: -2px; +} + +.dgg-scroller-theme.os-scrollbar-vertical .os-scrollbar-handle:before { + left: -6px; + right: -2px; +} + +.dgg-scroller-theme.os-scrollbar-rtl.os-scrollbar-vertical + .os-scrollbar-handle:before { + right: -6px; + left: -2px; +} + +.onstreamchat { + .os-scrollbar { + visibility: hidden !important; + } +} diff --git a/assets/chat/css/chat/_toolbar.scss b/assets/chat/css/chat/_toolbar.scss new file mode 100644 index 00000000..b325eb5c --- /dev/null +++ b/assets/chat/css/chat/_toolbar.scss @@ -0,0 +1,52 @@ +@use '../abstracts/' as a; + +$toolbar-icons-map: ( + 'emoticon': 'emotes', + 'whisper': 'whispers', + 'watching-focus': 'watching', + 'settings': 'settings', + 'users': 'users', +); + +#chat-tools-wrap { + display: flex; + position: relative; + user-select: none; +} + +.chat-tools-group { + display: flex; + + &:first-child { + flex: 1; + } +} + +.chat-tool-btn { + @keyframes whisper-pulse { + 2% { + opacity: 1; + transform: scale(1.3); + } + } + + &.ping .btn-icon { + animation: whisper-pulse 1.5s ease-in-out; + } + + @each $id, $icon in $toolbar-icons-map { + &#chat-#{$id}-btn .btn-icon { + @include a.icon-background('../img/icon-#{$icon}.svg'); + } + } +} + +#chat-whisper-unread-indicator { + left: 100%; + color: a.$color-light; + font-size: 0.75em; + position: absolute; + vertical-align: text-bottom; + margin-left: 0.5em; + top: 6px; +} diff --git a/assets/chat/css/chat/_window-select.scss b/assets/chat/css/chat/_window-select.scss new file mode 100644 index 00000000..9d925902 --- /dev/null +++ b/assets/chat/css/chat/_window-select.scss @@ -0,0 +1,77 @@ +@use '../abstracts/' as a; + +#chat-windows-select { + padding-top: 0.5em; + background: a.$color-surface-dark4; + white-space: nowrap; + display: flex; + z-index: 100; +} + +.win-main { + width: 3em; + flex-shrink: 0; + + span { + display: none; + } + + .tab-close { + display: none !important; + } + + .dgg-icon { + display: block; + width: 100%; + height: 100%; + + @include a.icon-background('../img/dgg-icon.svg'); + } +} + +.tab { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + border-radius: a.$bradius a.$bradius 0 0; + color: a.$color-chat-text3; + user-select: none; + cursor: pointer; + font-size: 1em; + padding: a.$gutter-sm a.$gutter-lg; + + &.active { + color: a.$color-chat-text1; + background: a.$color-surface-dark1; + margin-left: 0; + margin-right: 0; + overflow: visible; + text-overflow: unset; + min-width: auto; + display: flex; + align-items: center; + + .tab-close { + display: inline-block; + } + } + + &:hover, + &.unread { + color: a.$color-chat-text1; + } +} + +.tab-close { + width: 1em; + height: 1em; + opacity: 0.25; + margin-left: a.$gutter-md; + display: none; + + @include a.icon-background('../img/icon-close.svg'); + + &:hover { + opacity: 1; + } +} diff --git a/assets/chat/css/chat/event-bar/_event-bar-event.scss b/assets/chat/css/chat/event-bar/_event-bar-event.scss new file mode 100644 index 00000000..37bb53f2 --- /dev/null +++ b/assets/chat/css/chat/event-bar/_event-bar-event.scss @@ -0,0 +1,148 @@ +@use '../../abstracts/' as a; +@use '../../messages/event/_donation' as donation; + +.event-bar-event { + position: relative; + cursor: pointer; + transition: transform 100ms; + + font-size: 1.1em; + border-radius: 10px; + border-style: solid; + border-color: unset; + border-width: 2px; + margin: a.$gutter-sm; + + .event-contents { + display: flex; + padding: 0 a.$gutter-xs 0 0.4em; + background-color: a.$color-chat-emphasize; + justify-content: space-between; + align-items: center; + border-radius: 10px; + } + + .event-info { + margin-right: a.$gutter-xs; + max-width: 12ch; + overflow: hidden; + } + + .event-icon { + width: 2em; + height: 2em; + border: 0.25em solid transparent; + opacity: 0.75; + + &.subscription { + @include a.icon-background('../img/sub-regular.svg'); + } + + &.giftsub { + filter: invert(100%); + + @include a.icon-background('../img/sub-gift.png'); + } + + &.massgift { + filter: invert(100%); + + @include a.icon-background('../img/sub-mass-gift.png'); + } + } + + .user { + font-weight: 500; + color: a.$color-label-user; + display: inline-block; + animation: scrolling-event-username 12s linear 3s infinite; + word-wrap: normal; + + &::before { + content: unset; + } + + &:hover { + text-decoration: none; + } + } + + &.amount-0 { + border-color: #1c5cdb; + } + + @each $amount, $color in donation.$amount-color-map { + &.amount-#{$amount} { + border-color: $color; + + .event-icon { + filter: invert(100%); + + @include a.icon-background('../img/donation-amount-#{$amount}.png'); + } + } + } + + &.rainbow-border { + background-clip: padding-box; + border: solid 2px transparent; + + &:before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + margin: -2px; + border-radius: inherit; + background: var(--rainbow-gradient); + } + } + + &:hover { + transform: scale(1.05); + } + + // Ensure `removed` can override `enter` because `enter` is not removed from + // the event after the animation completes. + &.enter { + animation: event-bar-appear 500ms linear; + } + + &.removed { + animation: event-bar-disappear 500ms linear; + } +} + +@keyframes event-bar-appear { + 0% { + transform: scale(0.1); + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes event-bar-disappear { + 0% { + opacity: 1; + } + + 100% { + transform: scale(0.1); + opacity: 0; + } +} + +@keyframes scrolling-event-username { + 25%, + 50% { + transform: translateX(calc(-1 * max(0px, 100% - 12ch))); + } + + 75%, + 100% { + transform: translateX(0%); + } +} diff --git a/assets/chat/css/chat/event-bar/_index.scss b/assets/chat/css/chat/event-bar/_index.scss new file mode 100644 index 00000000..6a29d7f8 --- /dev/null +++ b/assets/chat/css/chat/event-bar/_index.scss @@ -0,0 +1,49 @@ +@use '../../abstracts/' as a; + +@use 'event-bar-event'; + +#chat-event-bar { + &:empty { + display: none; + } + + display: inline-flex; + overflow-x: scroll; + background: #111113; + z-index: 6; + + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +#chat-event-selected { + position: absolute; + z-index: 210; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + + .event-bar-selected-message { + margin: a.$gutter-sm; + + .focus:not(.watching-focus) & { + opacity: 1; + } + } + + &.hidden { + display: none; + } +} + +.onstreamchat { + #chat-event-bar { + display: none; + } + + #chat-event-selected, + #chat-pinned-message { + display: none; + } +} diff --git a/assets/chat/css/components/_button.scss b/assets/chat/css/components/_button.scss new file mode 100644 index 00000000..aad77269 --- /dev/null +++ b/assets/chat/css/components/_button.scss @@ -0,0 +1,29 @@ +@use '../abstracts/' as a; + +button.btn { + display: inline-block; + padding: a.$gutter-md a.$gutter-lg; + margin-bottom: 0; + font-weight: normal; + text-align: center; + white-space: nowrap; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: a.$bradius; +} + +.btn-primary { + color: a.$text-color; + background-color: a.$color-accent; + border-color: a.$color-accent; +} + +.btn-default { + color: a.$text-color3; + background-color: a.$color-surface-light1; + border-color: a.$color-surface-light3; +} diff --git a/assets/chat/css/components/_form-control.scss b/assets/chat/css/components/_form-control.scss new file mode 100644 index 00000000..29c1282e --- /dev/null +++ b/assets/chat/css/components/_form-control.scss @@ -0,0 +1,29 @@ +@use '../abstracts/' as a; + +.form-control { + color: a.$color-chat-text2; + background: a.$color-surface-dark1; + border: 1px solid lighten(a.$color-surface-dark1, 9); + + &:focus { + color: a.$color-chat-text1; + background: a.$color-surface-dark1; + border: 1px solid lighten(a.$color-surface-dark1, 15); + box-shadow: none; + outline: none; + } + + &[disabled], + &[readonly], + &::placeholder { + color: a.$color-chat-place; + } + + &[type='text'] { + width: 100%; + } +} + +textarea.form-control { + width: 100%; +} diff --git a/assets/icons/icons.scss b/assets/chat/css/components/_icon.scss similarity index 56% rename from assets/icons/icons.scss rename to assets/chat/css/components/_icon.scss index 893131d0..8afbac18 100644 --- a/assets/icons/icons.scss +++ b/assets/chat/css/components/_icon.scss @@ -1,8 +1,17 @@ -.icon-command, -.icon-error, +.btn-icon { + opacity: 0.25; + transition: opacity 150ms; + width: 100%; + height: 100%; + display: inline-block; + + &:hover, + &.active { + opacity: 1; + } +} + .icon-google, -.icon-info, -.icon-status, .icon-tv, .icon-twitch, .icon-twitter { @@ -10,31 +19,11 @@ background-repeat: no-repeat; display: inline-block; } -.icon-command { - background-position: -32px -0px; - width: 16px; - height: 16px; -} -.icon-error { - background-position: -0px -21px; - width: 16px; - height: 16px; -} .icon-google { background-position: -18px -21px; width: 16px; height: 16px; } -.icon-info { - background-position: -50px -0px; - width: 16px; - height: 16px; -} -.icon-status { - background-position: -50px -18px; - width: 16px; - height: 16px; -} .icon-tv { background-position: -0px -0px; width: 30px; diff --git a/assets/chat/css/components/_index.scss b/assets/chat/css/components/_index.scss new file mode 100644 index 00000000..8d083dae --- /dev/null +++ b/assets/chat/css/components/_index.scss @@ -0,0 +1,4 @@ +@use 'button'; +@use 'form-control'; +@use 'icon'; +@use 'tool-button'; diff --git a/assets/chat/css/components/_tool-button.scss b/assets/chat/css/components/_tool-button.scss new file mode 100644 index 00000000..08c7545c --- /dev/null +++ b/assets/chat/css/components/_tool-button.scss @@ -0,0 +1,12 @@ +@use '../abstracts/' as a; + +.chat-tool-btn { + width: 2.25em; + height: 2.25em; + cursor: pointer; + color: a.$color-chat-disabled; + position: relative; + text-decoration: none; + display: inline-block; + border: 0.35em solid transparent; /* used for "padding" */ +} diff --git a/assets/chat/css/menus/_emote-list.scss b/assets/chat/css/menus/_emote-list.scss new file mode 100644 index 00000000..931a65e8 --- /dev/null +++ b/assets/chat/css/menus/_emote-list.scss @@ -0,0 +1,61 @@ +@use '../abstracts/' as a; + +#chat-emote-list { + .lock { + width: 1em; + height: 1em; + opacity: 0.5; + display: inline-flex; + margin-right: a.$gutter-sm; + background: transparent url('../img/icon-lock.svg') no-repeat center center; + background-size: contain; + } + + .content { + text-align: center; + outline: 0; + } + + input { + padding: a.$gutter-lg a.$gutter-lg; + border: none; + background: none; + border-radius: 0; + } +} + +.emote-group { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: a.$gutter-lg; +} + +.favorite-emote { + position: relative; +} + +.favorite-emote:after { + content: url('../img/icon-pin.svg'); + position: absolute; + width: 1em; + height: 1em; + right: -0.5em; + top: -0.5em; + transform: rotate(45deg); +} + +.emote-item { + user-select: none; + padding: a.$gutter-sm; + margin: auto; + + .emote { + cursor: pointer; + + &.disabled { + cursor: not-allowed; + opacity: 0.5; + } + } +} diff --git a/assets/chat/css/menus/_emote-tooltip.scss b/assets/chat/css/menus/_emote-tooltip.scss new file mode 100644 index 00000000..6f3f605b --- /dev/null +++ b/assets/chat/css/menus/_emote-tooltip.scss @@ -0,0 +1,61 @@ +@use '../abstracts/' as a; + +#chat-emote-tooltip { + height: fit-content; + width: fit-content; + min-width: 75px; + max-width: 250px; + z-index: 221; + + .emote-image { + display: flex; + justify-content: center; + padding: a.$gutter-md; + } + + .emote-info { + border-top: 1px solid a.$color-surface-dark4; + padding: a.$gutter-sm; + + div { + display: flex; + justify-content: center; + + &.name { + font-weight: 500; + color: a.$text-color; + } + + &.tier, + &.creator { + color: a.$text-color1; + } + } + + button.favorite { + width: 100%; + display: flex; + justify-content: center; + margin-top: 5px; + + .btn-icon { + opacity: unset; + width: unset; + display: unset; + transition: background-color 500ms; + height: 20px; + aspect-ratio: 1; + mask: url('../img/icon-favorite.svg'); + background-color: a.$text-color2; + } + + &.favorited .btn-icon { + mask: url('../img/icon-favorite-fill.svg'); + } + + &:hover .btn-icon { + background-color: a.$color-yellow; + } + } + } +} diff --git a/assets/chat/css/menus/_event-action-menu.scss b/assets/chat/css/menus/_event-action-menu.scss new file mode 100644 index 00000000..b9190f6e --- /dev/null +++ b/assets/chat/css/menus/_event-action-menu.scss @@ -0,0 +1,23 @@ +@use '../abstracts/' as a; + +#event-action-menu { + height: fit-content; + width: fit-content; + min-width: 75px; + max-width: 250px; + z-index: 221; + + .chat-menu-inner { + background-color: a.$color-surface-dark3; + } + + .event-action { + transition: background-color 150ms ease; + color: a.$color-light; + padding: 0.5rem 1rem; + + &:hover { + background-color: a.$color-surface-dark4; + } + } +} diff --git a/assets/chat/css/menus/_index.scss b/assets/chat/css/menus/_index.scss new file mode 100644 index 00000000..6d9af991 --- /dev/null +++ b/assets/chat/css/menus/_index.scss @@ -0,0 +1,100 @@ +@use '../abstracts/' as a; + +@use 'emote-list'; +@use 'emote-tooltip'; +@use 'poll'; +@use 'settings'; +@use 'user-info'; +@use 'user-list'; +@use 'whispers-list'; +@use 'event-action-menu'; + +.chat-menu { + display: none; + position: absolute; + z-index: 220; + height: auto; + bottom: 6.3em; + max-width: 75%; + min-width: a.$chat-min-width; + width: 33.3333%; + right: 0; + top: 0; + + &.active { + display: block; + } + + .chat-menu-inner { + height: 100%; + display: flex; + flex-direction: column; + background-color: a.$color-surface-dark3; + } + + &.left { + left: 0; + right: auto; + + .chat-menu-inner { + border-bottom-right-radius: a.$bradius; + } + } + + &.right { + right: 0; + left: auto; + + .chat-menu-inner { + border-bottom-left-radius: a.$bradius; + } + } + + .floating-window { + height: fit-content !important; + } + + .scrollable { + flex: 1; + height: 100%; + } +} + +.toolbar { + position: relative; + border-bottom: 1px solid a.$color-surface-dark4; + top: auto; + left: auto; + + h5 { + justify-content: space-between; + padding: a.$gutter-md; + font-size: 1.1em; + font-weight: normal; + color: a.$text-color1; + align-items: center; + user-select: none; + display: flex; + margin: 0; + } + + span { + line-height: 1.25em; + display: inline-block; + } + + .chat-menu-close { + width: 1em; + height: 1em; + opacity: 0.5; + font-size: 1.2em; + display: inline-block; + cursor: pointer; + + @include a.icon-background('../img/icon-close.svg'); + + &:hover { + opacity: 1; + } + } +} diff --git a/assets/chat/css/menus/_poll.scss b/assets/chat/css/menus/_poll.scss new file mode 100644 index 00000000..79947bbd --- /dev/null +++ b/assets/chat/css/menus/_poll.scss @@ -0,0 +1,181 @@ +@use '../abstracts/' as a; + +#chat-poll-frame { + display: none; + transform: translateY(-100%); + transition: transform 250ms ease-out; + + &.active { + transform: translateY(0); + display: block; + } + + &:not(.poll-completed) { + .opt:hover { + cursor: pointer; + background-color: a.$color-surface-dark2; + } + } + + &.poll-completed { + .opt:not(.opt-winner) { + .opt-bar-inner { + background-color: a.$color-surface-dark4 !important; + } + + &.opt-marked { + .opt-info .opt-vote-number { + background-color: a.$color-surface-dark4; + } + } + } + + .opt-winner { + .opt-bar-inner { + background-color: a.$color-darkgreen !important; + } + + &.opt-marked { + .opt-info .opt-vote-number { + background-color: a.$color-darkgreen !important; + } + } + } + } +} + +.poll-frame { + padding: 0.5em 0.75em; + background: #2f2f2f; + border-radius: 4px; + margin: 4px; + color: a.$text-color; + + .poll-footer { + color: a.$text-color1; + margin: 0.75em 0 0.5em 0; + display: flex; + } + + .poll-timer { + background-color: a.$color-surface-dark3; + overflow: hidden; + padding: 3px; + flex: 1; + border-radius: 6px; + + .poll-timer-inner { + min-height: 0.3em; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); + background-color: a.$color-blue; + border-radius: 4px; + } + } +} + +.poll-header { + display: flex; + flex-direction: column; + width: 100%; + margin: 0.25em 0.1em 0.75em 0.1em; + + .poll-top { + display: flex; + justify-content: space-between; + width: 100%; + } + + .poll-title { + display: flex; + justify-content: space-between; + width: 100%; + opacity: 0.5; + + .poll-votes { + margin-right: a.$gutter-sm; + } + } + + .poll-question { + font-weight: 700; + } + + .poll-close { + width: 2em; + height: 1em; + opacity: 0.5; + font-size: 1.2em; + cursor: pointer; + + @include a.icon-background('../img/icon-close.svg'); + + &:hover { + opacity: 1; + } + } +} + +.opt { + display: block; + align-items: center; + margin-bottom: a.$gutter-md; + background-color: a.$color-surface-dark3; + border-radius: 6px; + padding: 4px; + + .opt-info { + display: flex; + + .opt-vote-number { + height: fit-content; + border-radius: 4px; + padding: 0 0.3em 0 0.3em; + margin: 0 0.3em 0.3em 0; + } + + .opt-bar-option { + font-weight: 500; + font-size: 0.85em; + color: a.$text-color; + margin: 0.1em 0em 0.25em 0; + display: flex; + flex-grow: 1; + } + + .opt-bar-value { + font-weight: 500; + font-size: 0.85em; + margin-right: a.$gutter-sm; + color: a.$text-color; + white-space: nowrap; + } + } + + .opt-bar { + flex: 1; + overflow: hidden; + background-color: a.$color-surface-dark2; + border-radius: 4px; + max-height: 0.75em; + + .opt-bar-inner { + padding: 0 0.25em; + min-height: 0.75em; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); + transition: all 750ms; + background-color: a.$color-darkred; + border-radius: 4px; + } + } + + &.opt-marked .opt-info .opt-vote-number { + background-color: a.$color-darkred; + color: a.$text-color; + } +} + +.onstreamchat { + #chat-poll-frame { + display: none !important; + } +} diff --git a/assets/chat/css/menus/_settings.scss b/assets/chat/css/menus/_settings.scss new file mode 100644 index 00000000..19d05939 --- /dev/null +++ b/assets/chat/css/menus/_settings.scss @@ -0,0 +1,53 @@ +@use '../abstracts/' as a; + +#chat-settings { + #chat-settings-form { + margin: a.$gutter-lg 0; + } + + .form-group { + margin: a.$gutter-lg a.$gutter-lg; + display: block; + position: relative; + } + + h4 { + font-size: 0.9em; + margin-top: a.$gutter-lg * 2; + margin-bottom: a.$gutter-lg; + padding-left: a.$gutter-lg; + color: a.$text-color-disabled; + text-transform: uppercase; + font-weight: 600; + } + + label { + display: inline-block; + font-weight: normal; + max-width: 100%; + margin-bottom: a.$gutter-md; + } + + .checkbox label { + margin-bottom: 0; + font-weight: 400; + max-width: 100%; + cursor: pointer; + display: flex; + justify-items: center; + align-items: center; + } + + .checkbox input { + margin: 0 a.$gutter-sm 0 0; + line-height: normal; + box-sizing: border-box; + padding: 0; + } + + select { + border-radius: a.$bradius; + padding: a.$gutter-sm; + width: 100%; + } +} diff --git a/assets/chat/css/menus/_user-info.scss b/assets/chat/css/menus/_user-info.scss new file mode 100644 index 00000000..b1129101 --- /dev/null +++ b/assets/chat/css/menus/_user-info.scss @@ -0,0 +1,97 @@ +@use '../abstracts/' as a; + +$toolbar-icons-map: ( + 'mute': 'mute', + 'ban': 'ban', + 'logs': 'logs', + 'whisper': 'whispers', + 'ignore': 'ignore', + 'unignore': 'unignore', +); + +#chat-user-info { + height: fit-content; + bottom: unset; + min-width: 250px; + max-width: 350px; + + .floating-window { + box-shadow: 0px 0px 6px black; + border-radius: 6px; + } + + .user-info h5 { + padding-left: a.$gutter-md; + margin-top: unset; + margin-bottom: unset; + border-bottom: 1px solid a.$color-surface-dark4; + } + + .flairs { + margin-top: a.$gutter-lg; + margin-bottom: a.$gutter-lg; + padding-left: a.$gutter-md; + } + + .features { + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; + justify-items: center; + margin-left: -0.2em; + } + + .stalk { + height: 100px; + + .scrollable { + background: a.$color-chat-bg; + } + } + + .chat-tool-btn { + &:hover, + &.active { + .btn-icon { + opacity: 1; + } + } + + @each $id, $icon in $toolbar-icons-map { + &##{$id}-user-btn .btn-icon { + @include a.icon-background('../img/icon-#{$icon}.svg'); + } + } + } + + .hidden { + display: none !important; + } +} + +.action-buttons { + margin: a.$gutter-md 0 a.$gutter-md 0; + display: grid; + grid-auto-flow: column; + justify-content: space-around; +} + +#action-durations { + height: calc(20px + 2 * a.$gutter-md); + display: flex; + justify-content: space-around; + text-align: center; + font-size: 1.2em; + + .ban-duration-button, + .mute-duration-button { + opacity: 0.25; + transition: opacity 150ms; + color: white; + width: 100%; + + &:hover { + opacity: 1; + } + } +} diff --git a/assets/chat/css/menus/_user-list.scss b/assets/chat/css/menus/_user-list.scss new file mode 100644 index 00000000..10e45d14 --- /dev/null +++ b/assets/chat/css/menus/_user-list.scss @@ -0,0 +1,98 @@ +@use '../abstracts/' as a; + +#chat-user-list { + .section { + margin: 1.5em 0em 0.5em; + + .title { + padding: 0em 0em 0.2em 1em; + font-weight: 600; + border-bottom: 1px solid; + + .features { + float: right; + display: inline-flex; + vertical-align: text-top; + align-items: center; + margin-right: a.$gutter-md; + + .flair { + cursor: pointer; + margin-right: a.$gutter-xs; + } + } + } + } + + .content { + margin-top: -(a.$gutter-md); + padding-bottom: a.$gutter-lg; + } + + .scrollable { + max-height: calc(100% - 3em); + } + + &.search-in { + .user-entry { + display: none; + + &.found { + display: flex; + } + } + } + + input { + padding: a.$gutter-lg a.$gutter-lg; + border: none; + background: none; + border-radius: 0; + } +} + +.user-entry { + margin: 0; + cursor: pointer; + padding: 0 a.$gutter-lg; + text-decoration: none; + visibility: visible; + align-items: center; + display: flex; + + .user-actions { + position: absolute; + right: a.$gutter-md; + + .mention-nick, + .whisper-nick { + visibility: hidden; + margin-right: a.$gutter-xs; + margin-top: 0.45em; + display: inline-block; + opacity: 0.25; + width: 1em; + height: 1em; + &:hover { + opacity: 1; + } + } + + .mention-nick { + @include a.icon-background('../img/icon-share.svg'); + } + + .whisper-nick { + @include a.icon-background('../img/icon-whisper.svg'); + } + } + + &:hover { + background: #282828; + + .mention-nick, + .whisper-nick { + visibility: visible; + } + } +} diff --git a/assets/chat/css/menus/_whispers-list.scss b/assets/chat/css/menus/_whispers-list.scss new file mode 100644 index 00000000..31adea0e --- /dev/null +++ b/assets/chat/css/menus/_whispers-list.scss @@ -0,0 +1,78 @@ +@use '../abstracts/' as a; + +#chat-whisper-users { + .content > ul { + margin: 0; + padding: a.$gutter-md 0; + } + + .unread-0 { + .badge { + display: none; + } + + .user, + .user:hover { + color: a.$text-color1; + } + } + + .empty { + color: a.$text-color1; + margin: a.$gutter-lg; + } +} + +.conversation { + list-style: none; + position: relative; + cursor: pointer; + padding-left: a.$gutter-md; + display: flex; + align-items: center; + + .user { + color: a.$color-accent; + display: block; + + &:hover { + color: a.$color-accent-light; + + .badge { + color: a.$color-chat-text1; + } + } + } + + .badge, + .remove { + float: right; + margin-right: a.$gutter-md; + } + + .badge { + font-size: 0.8em; + padding: 0 a.$gutter-md; + display: inline-block; + background: a.$color-surface-dark4; + border-radius: 50%; + margin: a.$gutter-xs 0; + color: a.$color-chat-text2; + } + + .remove { + opacity: 0.25; + display: inline-block; + text-decoration: none; + text-align: center; + line-height: 1.3em; + width: 1.3em; + height: 1.3em; + + @include a.icon-background('../img/icon-close.svg'); + + &:hover { + opacity: 1; + } + } +} diff --git a/assets/chat/css/messages/_base.scss b/assets/chat/css/messages/_base.scss new file mode 100644 index 00000000..d8349e4f --- /dev/null +++ b/assets/chat/css/messages/_base.scss @@ -0,0 +1,135 @@ +@use '../abstracts' as a; + +$link-color-map: ( + 'nsfw': a.$color-underline-nsfw, + 'nsfl': a.$color-underline-nsfl, + 'spoilers': a.$color-underline-spoilers, +); + +.msg-chat { + word-wrap: break-word; + padding: a.$gutter-xs a.$gutter-md * 2 a.$gutter-xs a.$gutter-md; + color: a.$color-chat-text2; + + .time { + font-size: 0.8em; + color: a.$color-chat-disabled; + padding-right: a.$gutter-sm; + display: none; /* hidden by default */ + } + + .user { + font-weight: 500; + cursor: pointer; + margin-left: a.$gutter-sm; + color: a.$color-label-user; + } + + .chat-user { + cursor: pointer; + position: relative; + + &:hover { + text-decoration: underline; + } + } + + .greentext { + color: a.$color-green-text; + } + + .sus { + text-transform: uppercase; + font-family: 'Among Us', a.$chat-lines-font; + } + + .externallink { + border-style: solid; + border-color: transparent; + border-width: 1px 0 1px 0; + color: a.$color-link; + position: relative; + + &:visited { + color: a.$color-link-visited; + } + + &:hover, + &:focus { + color: a.$color-link-hover; + } + } + + @each $type, $color in $link-color-map { + .#{$type}-link { + border-style: dashed; + border-width: 1px 0 1px 0; + border-color: transparent transparent $color transparent; + } + } + + .hidden { + display: none !important; + } + + .embed-button { + position: relative; + top: 2.5px; + margin-left: a.$gutter-sm; + margin-right: a.$gutter-xs; + opacity: 0.5; + + &:hover { + opacity: 1; + } + } + + .focus & { + opacity: 0.3; + } +} + +.pref-showtime .time { + display: inline; +} + +.pref-disableanimations { + a.user, + span.user { + animation: none !important; + } +} + +.onstreamchat { + .msg-chat { + color: a.$text-color; + margin: 0; + padding: 1px 20px 1px 5px; + filter: drop-shadow(-1px -1px 0 rgba(black, 0.5)) + drop-shadow(1px -1px 0 rgba(black, 0.5)) + drop-shadow(-1px 1px 0 rgba(black, 0.5)) + drop-shadow(1px 1px 0 rgba(black, 0.5)); + letter-spacing: 0.03em; + } + + a.externallink, + a.externallink:hover { + color: a.$color-accent-light; + } + + .greentext { + color: a.$text-color; + } + + .msg-chat, + .msg-chat:not(.rainbow-border):before, + .greentext { + background: none !important; + } + + time, + .time, + .msg-chat .text:before { + display: none !important; + } +} diff --git a/assets/chat/css/messages/_index.scss b/assets/chat/css/messages/_index.scss new file mode 100644 index 00000000..77ddd9f8 --- /dev/null +++ b/assets/chat/css/messages/_index.scss @@ -0,0 +1,9 @@ +@use '../abstracts' as a; + +@use 'base'; +@use 'ui'; +@use 'user'; +@use 'emote'; +@use 'event'; +@use 'pinned'; +@use 'modifiers'; diff --git a/assets/chat/css/messages/emote/_combo.scss b/assets/chat/css/messages/emote/_combo.scss new file mode 100644 index 00000000..dbef0908 --- /dev/null +++ b/assets/chat/css/messages/emote/_combo.scss @@ -0,0 +1,186 @@ +@use '../../abstracts/' as a; + +.chat-combo { + color: a.$color-chat-text1; + display: inline-block; + position: relative; + line-height: 2em; + padding-left: a.$gutter-md; + + i { + font-style: normal; + display: inline-block; + vertical-align: middle; + } + + .hit, + .combo, + .count, + .x { + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; + } + + .combo { + display: none; + color: a.$text-color1; + font-weight: normal; + } + + .hit { + color: a.$color-light; + animation: emote-hit 600ms 1; + font-style: italic; + text-transform: uppercase; + margin-left: 0; + letter-spacing: 0.05em; + display: inline-block; + } + + .count { + font-size: 1.15em; + letter-spacing: -0.05em; + font-weight: bold; + } + + &.x5 { + .count { + font-size: 150%; + } + } + + &.x10 { + .count { + font-size: 200%; + } + + .combo { + font-weight: bold; + } + } + + &.x20 { + .count { + font-size: 300%; + } + + .combo { + font-weight: bold; + } + } + + &.x30 { + .count { + font-size: 400%; + } + + .combo { + font-weight: bold; + } + } + + &.x50 { + .count { + font-size: 500%; + } + + .combo { + font-weight: bold; + } + } + + &.combo-complete { + &.x10, + &.x20, + &.x30, + &.x50 { + animation: emote-greyout 3500ms 1; + background: transparent url('../img/emote-splat.png') no-repeat center + center; + background-size: 100% 100%; + filter: grayscale(100%); + + .combo { + color: a.$color-light; + margin-left: a.$gutter-md; + } + } + + .combo { + animation: emote-complete 1000ms 1; + display: inline-block; + } + + .hit { + display: none; + } + } +} + +.onstreamchat { + .chat-combo { + background-image: none !important; + } + + .combo { + color: a.$text-color; + } +} + +@keyframes emote-hit { + 0% { + color: #b91010; + font-size: 200%; + } + 1% { + color: a.$color-light; + font-size: 190%; + } + 2% { + color: #b91010; + font-size: 200%; + } + 3% { + color: a.$color-light; + font-size: 190%; + } + 4% { + color: #b91010; + font-size: 200%; + } + 100% { + color: a.$color-light; + font-size: 120%; + } +} + +@keyframes emote-complete { + 0% { + transform: translate(-10px, 0); + opacity: 0; + } + 2% { + transform: translate(10px, 0); + color: white; + opacity: 1; + } + 100% { + transform: translate(0, 0); + opacity: 1; + } +} + +@keyframes emote-greyout { + 0% { + filter: grayscale(0); + } + 75% { + filter: grayscale(0); + } + 100% { + filter: grayscale(100%); + } +} diff --git a/assets/chat/css/messages/emote/_index.scss b/assets/chat/css/messages/emote/_index.scss new file mode 100644 index 00000000..33bd419a --- /dev/null +++ b/assets/chat/css/messages/emote/_index.scss @@ -0,0 +1,28 @@ +@use 'combo'; + +.emote { + display: inline-block; + position: relative; + overflow: hidden; + text-indent: -999em; +} + +.watching-focus:not(.focus) { + .msg-emote { + opacity: 0.3; + + &.watching-same, + &.msg-highlight { + opacity: 1; + } + } +} + +.pref-disableanimations { + .emote, + .emote:hover, + .emote::before, + .emote::after { + animation: none !important; + } +} diff --git a/assets/chat/css/messages/event/_broadcast.scss b/assets/chat/css/messages/event/_broadcast.scss new file mode 100644 index 00000000..ef345845 --- /dev/null +++ b/assets/chat/css/messages/event/_broadcast.scss @@ -0,0 +1,11 @@ +@use '../../abstracts/' as a; + +.msg-broadcast { + border-color: a.$color-text-broadcast; + + .broadcast-icon { + filter: invert(100%); + + @include a.icon-background('../img/icon-broadcast.png'); + } +} diff --git a/assets/chat/css/messages/event/_donation.scss b/assets/chat/css/messages/event/_donation.scss new file mode 100644 index 00000000..ac3f2927 --- /dev/null +++ b/assets/chat/css/messages/event/_donation.scss @@ -0,0 +1,29 @@ +@use '../../abstracts/' as a; + +$amount-color-map: ( + 5: #59aeea, + 10: #2addc8, + 25: #4db524, + 50: #dd29d2, + 100: #e62117, +); + +.msg-donation { + &.amount-0 { + border-color: #1c5cdb; + } + + @each $amount, $color in $amount-color-map { + &.amount-#{$amount} { + border-color: $color; + } + + .donation-icon { + &.amount-#{$amount} { + filter: invert(100%); + + @include a.icon-background('../img/donation-amount-#{$amount}.png'); + } + } + } +} diff --git a/assets/chat/css/messages/event/_event.scss b/assets/chat/css/messages/event/_event.scss new file mode 100644 index 00000000..28956bb1 --- /dev/null +++ b/assets/chat/css/messages/event/_event.scss @@ -0,0 +1,138 @@ +@use '../../abstracts/' as a; + +.msg-donation, +.msg-subscription, +.msg-giftsub, +.msg-massgift, +.msg-broadcast { + text-shadow: 1px 1px 3px rgba(0, 0, 0, 1); + font-size: 1.1em; + font-weight: 400; + padding: unset; + border-radius: 10px; + border-style: solid; + border-color: unset; + border-width: 3px; + display: flex; + flex-direction: column; + margin: a.$gutter-sm a.$gutter-md * 2 a.$gutter-sm a.$gutter-sm; + + .event-wrapper { + border-radius: 10px; + } + + .event-top { + display: flex; + padding: a.$gutter-xs a.$gutter-md * 2 a.$gutter-xs a.$gutter-md; + background-color: a.$color-chat-emphasize; + justify-content: space-between; + align-items: center; + border-top-right-radius: 10px; + border-top-left-radius: 10px; + } + + .event-icon { + color: a.$color-chat-disabled; + position: relative; + text-decoration: none; + display: inline-block; + border: 0.25em solid transparent; + flex-shrink: 0; + opacity: 0.75; + width: 100%; + height: 100%; + transition: background 200ms ease; + } + + .event-bottom { + padding: a.$gutter-xs a.$gutter-md * 2 a.$gutter-xs a.$gutter-md; + background-color: a.$color-chat-bg; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + } + + .event-button { + width: 2.25em; + height: 2.25em; + + &:hover:not(:disabled) { + .event-icon { + @include a.icon-background('../img/icon-ellipsis-vertical.svg'); + } + } + + &:disabled { + cursor: default; + } + } + + &:not(:has(.event-bottom)) { + .event-top { + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + } + } + + .user { + margin-left: unset; + text-shadow: none; + } + + &.censored { + .text { + display: none; + } + + .event-bottom:after { + color: a.$color-accent; + cursor: pointer; + content: ''; + } + + .event-bottom:hover:after { + color: a.$color-accent-light; + } + } +} + +.onstreamchat { + .msg-donation, + .msg-subscription, + .msg-giftsub, + .msg-massgift, + .msg-broadcast { + text-shadow: none; + padding: 0; + border-style: none none none solid; + border-width: 6px; + border-radius: 0; + + .event-top, + .event-bottom { + padding: 0 0 0 a.$gutter-md; + background: none; + } + + &.rainbow-border { + border-style: none none none solid; + border-width: 6px; + border-radius: 0; + + &:before { + content: ''; + position: absolute; + top: unset; + right: 100%; + bottom: unset; + left: unset; + z-index: -1; + margin: -1px 0 0 0; + border-radius: inherit; + background: var(--rainbow-gradient); + aspect-ratio: 1/1; + height: 100%; + transform: rotate(90deg); + } + } + } +} diff --git a/assets/chat/css/messages/event/_index.scss b/assets/chat/css/messages/event/_index.scss new file mode 100644 index 00000000..64166156 --- /dev/null +++ b/assets/chat/css/messages/event/_index.scss @@ -0,0 +1,7 @@ +@use '../../abstracts/' as a; + +@use 'event'; + +@use 'broadcast'; +@use 'donation'; +@use 'subscription'; diff --git a/assets/chat/css/messages/event/_subscription.scss b/assets/chat/css/messages/event/_subscription.scss new file mode 100644 index 00000000..0798a3ae --- /dev/null +++ b/assets/chat/css/messages/event/_subscription.scss @@ -0,0 +1,67 @@ +@use '../../abstracts/' as a; + +.msg-subscription, +.msg-giftsub, +.msg-massgift { + position: relative; + + .subscription-icon { + &.regular { + @include a.icon-background('../img/sub-regular.svg'); + } + + &.gift { + filter: invert(100%); + + @include a.icon-background('../img/sub-gift.png'); + } + + &.mass-gift { + filter: invert(100%); + + @include a.icon-background('../img/sub-mass-gift.png'); + } + } + + .streak { + display: flex; + flex-direction: column; + width: calc(100% - 2.25em - a.$gutter-md * 2); + } + + .streak-message { + border-top-style: solid; + border-top-width: 1px; + border-top-color: a.$color-chat-place; + padding-top: 2px; + margin-top: 2px; + } + + .tier { + font-weight: 500; + cursor: unset; + + &:hover { + text-decoration: unset; + &:before { + content: unset !important; + } + } + } + + &.rainbow-border { + background-clip: padding-box; + border: solid 3px transparent; + border-radius: 10px; + + &:before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + margin: -3px; + border-radius: inherit; + background: var(--rainbow-gradient); + } + } +} diff --git a/assets/chat/css/messages/modifiers/_censored.scss b/assets/chat/css/messages/modifiers/_censored.scss new file mode 100644 index 00000000..34bbe952 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_censored.scss @@ -0,0 +1,22 @@ +@use '../../abstracts/' as a; + +.censored { + .text { + display: none; + } + + .ctrl { + display: inline-block; + + &:after { + color: a.$color-accent; + cursor: pointer; + padding-left: a.$gutter-md; + content: ''; + } + + &:hover:after { + color: a.$color-accent-light; + } + } +} diff --git a/assets/chat/css/messages/modifiers/_continue.scss b/assets/chat/css/messages/modifiers/_continue.scss new file mode 100644 index 00000000..d3a940e9 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_continue.scss @@ -0,0 +1,19 @@ +@use '../../abstracts/' as a; + +.msg-continue { + .text:before { + color: a.$color-chat-disabled; + content: '> '; + } + + .features, + .user { + display: none; + } +} + +.onstreamchat { + .continue { + visibility: hidden !important; + } +} diff --git a/assets/chat/css/messages/modifiers/_highlight.scss b/assets/chat/css/messages/modifiers/_highlight.scss new file mode 100644 index 00000000..fe814467 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_highlight.scss @@ -0,0 +1,11 @@ +@use '../../abstracts/' as a; + +.msg-highlight { + color: a.$color-chat-text1; + background-color: a.$color-chat-highlight; + + time, + .continue { + color: a.$color-chat-text3; + } +} diff --git a/assets/chat/css/messages/modifiers/_historical.scss b/assets/chat/css/messages/modifiers/_historical.scss new file mode 100644 index 00000000..134cc459 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_historical.scss @@ -0,0 +1,25 @@ +@use '../../abstracts/' as a; + +.msg-historical { + color: a.$text-color1; + + .time { + display: inline !important; + } + + &:before { + background: none !important; + } +} + +.chat-output:not(.chat-win-main) { + .msg-historical { + background-color: a.$color-chat-emphasize; + color: a.$color-chat-text1; + } + + .msg-historical.msg-own { + background: none !important; + color: a.$color-chat-text2; + } +} diff --git a/assets/chat/css/messages/modifiers/_index.scss b/assets/chat/css/messages/modifiers/_index.scss new file mode 100644 index 00000000..1be3723c --- /dev/null +++ b/assets/chat/css/messages/modifiers/_index.scss @@ -0,0 +1,9 @@ +@use '../../abstracts/' as a; + +@use 'censored'; +@use 'continue'; +@use 'highlight'; +@use 'historical'; +@use 'me'; +@use 'own'; +@use 'tagged'; diff --git a/assets/chat/css/messages/modifiers/_me.scss b/assets/chat/css/messages/modifiers/_me.scss new file mode 100644 index 00000000..5c0de314 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_me.scss @@ -0,0 +1,3 @@ +.msg-me .text { + font-style: italic; +} diff --git a/assets/chat/css/messages/modifiers/_own.scss b/assets/chat/css/messages/modifiers/_own.scss new file mode 100644 index 00000000..aa66c661 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_own.scss @@ -0,0 +1,6 @@ +@use '../../abstracts/' as a; + +.msg-own { + background-color: a.$color-chat-emphasize; + color: a.$color-chat-text1; +} diff --git a/assets/chat/css/messages/modifiers/_tagged.scss b/assets/chat/css/messages/modifiers/_tagged.scss new file mode 100644 index 00000000..290bf3b7 --- /dev/null +++ b/assets/chat/css/messages/modifiers/_tagged.scss @@ -0,0 +1,40 @@ +@use '../../abstracts/' as a; + +$tag-colors: ( + 'green': a.$color-green, + 'yellow': a.$color-yellow, + 'orange': a.$color-orange, + 'red': a.$color-red, + 'purple': a.$color-purple, + 'blue': a.$color-blue, + 'sky': a.$color-sky, + 'lime': a.$color-lime, + 'pink': a.$color-pink, + 'black': a.$color-black, +); + +.msg-tagged { + position: relative; + + &:before { + position: absolute; + top: 0; + left: 0; + bottom: 0; + min-width: 0.45em; + display: block; + background: a.$color-black; + content: ''; + } + + @each $name, $color in $tag-colors { + &.msg-tagged-#{$name}:before { + background-color: $color; + } + } +} + +// Prioritize highlighted message background color +.pref-taggedvisibility .msg-tagged:not(.msg-highlight) { + background-color: a.$color-chat-tagged; +} diff --git a/assets/chat/css/messages/pinned/_frame.scss b/assets/chat/css/messages/pinned/_frame.scss new file mode 100644 index 00000000..b7c5644d --- /dev/null +++ b/assets/chat/css/messages/pinned/_frame.scss @@ -0,0 +1,44 @@ +@use '../../abstracts/' as a; + +#chat-pinned-message { + display: none; + padding: 4px; + position: absolute; + width: 100%; + z-index: 210; + + &.active { + display: block; + } +} + +#chat-pinned-show-btn { + display: none; + width: 1.3em; + height: 1.3em; + cursor: pointer; + color: a.$color-chat-disabled; + position: absolute; + text-decoration: none; + right: 0; + margin-top: 2px; + margin-right: a.$gutter-md; + + &.active { + display: block; + } + + .btn-icon { + @include a.icon-background('../img/icon-pin.svg'); + } + + &:hover .btn-icon { + opacity: 1; + } +} + +.onstreamchat { + #chat-pinned-message { + display: none !important; + } +} diff --git a/assets/chat/css/messages/pinned/_index.scss b/assets/chat/css/messages/pinned/_index.scss new file mode 100644 index 00000000..92f7438b --- /dev/null +++ b/assets/chat/css/messages/pinned/_index.scss @@ -0,0 +1,73 @@ +@use '../../abstracts/' as a; + +@use 'frame'; + +.msg-pinned { + background-color: a.$color-chat-emphasize; + padding-top: a.$gutter-md; + padding-bottom: a.$gutter-md; + border-style: solid; + border-radius: 10px; + border-color: #505050; + transition: all 0.15s ease-in-out; + + &:hover { + border-color: a.$color-chat-text3; + } + + &.hidden { + display: none; + } + + .user { + text-shadow: none; + } + + .chat-tool-btn { + width: 1.5em; + height: 1.5em; + cursor: pointer; + color: a.$color-chat-disabled; + position: relative; + text-decoration: none; + float: right; + border: unset; + + &:hover, + &.active { + .btn-icon { + opacity: 1; + } + } + } + + #close-pin-btn .btn-icon { + @include a.icon-background('../img/icon-close.svg'); + } + + #unpin-btn .btn-icon { + @include a.icon-background('../img/icon-unpin.svg'); + } + + .focus & { + opacity: 1; + } +} + +.onstreamchat { + .msg-pinned { + position: unset; + text-shadow: unset; + background-color: unset; + font-size: unset; + font-weight: unset; + padding-top: unset; + padding-bottom: unset; + box-shadow: unset; + border-radius: unset; + + .chat-tool-btn { + display: none !important; + } + } +} diff --git a/assets/chat/css/messages/ui/_command.scss b/assets/chat/css/messages/ui/_command.scss new file mode 100644 index 00000000..b872f8c2 --- /dev/null +++ b/assets/chat/css/messages/ui/_command.scss @@ -0,0 +1,5 @@ +@use '../../abstracts/' as a; + +.msg-command .text:before { + @include a.msg-icon(-32px, -0px); +} diff --git a/assets/chat/css/messages/ui/_error.scss b/assets/chat/css/messages/ui/_error.scss new file mode 100644 index 00000000..ef1c7663 --- /dev/null +++ b/assets/chat/css/messages/ui/_error.scss @@ -0,0 +1,5 @@ +@use '../../abstracts/' as a; + +.msg-error .text:before { + @include a.msg-icon(-0px, -21px); +} diff --git a/assets/chat/css/messages/ui/_index.scss b/assets/chat/css/messages/ui/_index.scss new file mode 100644 index 00000000..97d9d692 --- /dev/null +++ b/assets/chat/css/messages/ui/_index.scss @@ -0,0 +1,31 @@ +@use '../../abstracts/' as a; + +@use 'command'; +@use 'error'; +@use 'info'; +@use 'status'; + +.msg-command, +.msg-error, +.msg-info, +.msg-status { + white-space: pre-wrap; + color: a.$color-chat-text1; + + .text:before { + content: ''; + display: inline-block; + vertical-align: text-top; + margin-right: a.$gutter-sm; + } + + &.msg-continue .text:before { + content: ''; + } +} + +.onstreamchat { + .msg-ui { + display: none !important; + } +} diff --git a/assets/chat/css/messages/ui/_info.scss b/assets/chat/css/messages/ui/_info.scss new file mode 100644 index 00000000..ed9c17a2 --- /dev/null +++ b/assets/chat/css/messages/ui/_info.scss @@ -0,0 +1,15 @@ +@use '../../abstracts/' as a; + +.msg-info { + color: unset; +} + +.msg-info .text:before { + @include a.msg-icon(-50px, -0px); +} + +.onstreamchat { + .msg-info { + display: none !important; + } +} diff --git a/assets/chat/css/messages/ui/_status.scss b/assets/chat/css/messages/ui/_status.scss new file mode 100644 index 00000000..f6065de6 --- /dev/null +++ b/assets/chat/css/messages/ui/_status.scss @@ -0,0 +1,11 @@ +@use '../../abstracts/' as a; + +.msg-status .text:before { + @include a.msg-icon(-50px, -18px); +} + +.onstreamchat { + .msg-status { + @include a.slide-in; + } +} diff --git a/assets/chat/css/messages/user/_death.scss b/assets/chat/css/messages/user/_death.scss new file mode 100644 index 00000000..72c4d5d3 --- /dev/null +++ b/assets/chat/css/messages/user/_death.scss @@ -0,0 +1,27 @@ +@use '../../abstracts/' as a; + +.msg-death { + .text { + font-style: italic; + } + + .user:before { + content: ''; + display: inline-block; + vertical-align: text-top; + margin-right: a.$gutter-sm; + + @include a.msg-icon(-32px, -0px); + } +} + +.watching-focus:not(.focus) { + .msg-death { + opacity: 0.3; + + &.watching-same, + &.msg-highlight { + opacity: 1; + } + } +} diff --git a/assets/chat/css/messages/user/_index.scss b/assets/chat/css/messages/user/_index.scss new file mode 100644 index 00000000..83ed7413 --- /dev/null +++ b/assets/chat/css/messages/user/_index.scss @@ -0,0 +1,43 @@ +@use '../../abstracts/' as a; + +@use 'death'; +@use 'whisper'; + +.msg-user { + .features { + display: inline-flex; + vertical-align: text-top; + align-items: center; + margin-left: -(a.$gutter-xs); + } + + .flair { + cursor: pointer; + margin-left: a.$gutter-xs; + } +} + +.watching-focus:not(.focus) { + .msg-user { + opacity: 0.3; + + &.watching-same, + &.msg-highlight { + opacity: 1; + } + } +} + +.pref-hideflairicons .features { + display: none; +} + +.onstreamchat { + .msg-user { + @include a.slide-in; + } + + .features { + display: none !important; + } +} diff --git a/assets/chat/css/messages/user/_whisper.scss b/assets/chat/css/messages/user/_whisper.scss new file mode 100644 index 00000000..5a051d46 --- /dev/null +++ b/assets/chat/css/messages/user/_whisper.scss @@ -0,0 +1,19 @@ +@use '../../abstracts/' as a; + +.msg-whisper { + background-color: a.$color-chat-emphasize; + + a.user { + background: a.$color-accent; + padding: a.$gutter-xs a.$gutter-md a.$gutter-xs a.$gutter-md; + border-radius: a.$bradius; + font-weight: normal; + color: white; + + &:hover { + text-decoration: none; + color: a.$color-accent; + background: white; + } + } +} diff --git a/assets/chat/css/style.scss b/assets/chat/css/style.scss index b6b8447b..64d9fe72 100644 --- a/assets/chat/css/style.scss +++ b/assets/chat/css/style.scss @@ -1,41 +1,69 @@ -@import '../../common'; -@import '../../icons/icons'; -@import '~normalize.css'; -@import '~tippy.js/dist/tippy.css'; -@import '~tippy.js/dist/svg-arrow.css'; +@use '~normalize.css/normalize'; +@use '~tippy.js/dist/tippy'; +@use '~tippy.js/dist/svg-arrow'; +@use 'abstracts' as a; + +@use 'chat'; +@use 'menus'; +@use 'messages'; +@use 'components'; + @import url('https://fonts.googleapis.com/css?family=Roboto:400,500,600,700'); +@font-face { + font-family: 'Among Us'; + src: url('../../../fonts/AmongUs-Regular.ttf') format('truetype'); +} + *, *::before, *::after { box-sizing: inherit; } + html { box-sizing: border-box; + position: relative; + color: a.$text-color1; + background: a.$color-chat-bg; + font-family: a.$chat-chrome-font; + font-size: 13px; + line-height: 1.42857143; + text-rendering: optimizeLegibility; + font-feature-settings: 'kern'; + height: 100%; + margin: 0; + padding: 0; } -$chat-chrome-font: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; -$chat-lines-font: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; -$chat-min-width: 300px; - -$gutter-xs: 0.2em; -$gutter-sm: 0.3em; -$gutter-md: 0.6em; -$gutter-lg: 0.9em; - -$bradius: 0.25em; - -html, body { position: relative; - color: $text-color1; - background: $color-chat-bg; - font-family: $chat-chrome-font; + color: a.$text-color1; + background: a.$color-chat-bg; + font-family: a.$chat-chrome-font; font-size: 13px; line-height: 1.42857143; text-rendering: optimizeLegibility; font-feature-settings: 'kern'; + height: 100%; + margin: 0; + padding: 0; + + &.pref-fontscale { + &[data-fontscale='small'] { + font-size: 14px; + } + + &[data-fontscale='medium'] { + font-size: 15px; + } + + &[data-fontscale='large'] { + font-size: 16px; + } + } } + textarea, input, button { @@ -55,11 +83,32 @@ button { background: transparent; } -html, -body { - height: 100%; - margin: 0; - padding: 0; +a { + color: a.$color-accent; + text-decoration: none; + + &:hover, + &:focus { + color: a.$color-accent-light; + text-decoration: underline; + } +} + +hr { + border: 0; + margin: 1em 0; + border-top: 1px solid darken(a.$color-surface-dark1, 50%); + border-bottom: 1px solid a.$color-surface-dark3; +} + +.tippy-box[data-theme~='dgg'] { + text-align: center; + background-color: white; + color: #000; +} + +.tippy-box[data-theme~='dgg'] > .tippy-svg-arrow { + fill: white; } /* Small screen, non-retina */ @@ -110,2158 +159,23 @@ body { } } -body.pref-fontscale[data-fontscale='small'] { - font-size: 14px; -} -body.pref-fontscale[data-fontscale='medium'] { - font-size: 15px; -} -body.pref-fontscale[data-fontscale='large'] { - font-size: 16px; -} - -a { - color: $color-accent; - text-decoration: none; -} -a:hover, -a:focus { - color: $color-accent-light; - text-decoration: underline; -} -hr { - border: 0; - margin: 1em 0; - border-top: 1px solid darken($color-surface-dark1, 50%); - border-bottom: 1px solid $color-surface-dark3; -} - -/* Layout */ -#chat { - min-width: $chat-min-width; - display: flex; - flex-direction: column; - position: relative; - width: 100%; - height: 100%; - overflow: hidden; -} -#chat-output-frame { - flex: 1; - overflow: hidden; - width: 100%; -} -.chat-output { - width: 100%; - height: 100%; -} -#chat-input-frame { - padding: $gutter-md $gutter-md 0 $gutter-md; -} -#chat-input-wrap { - position: relative; -} -@keyframes whisper-pulse { - 2% { - opacity: 1; - transform: scale(1.3); - } -} -#chat-tools-wrap { - display: flex; - position: relative; - user-select: none; - .chat-tools-group { - display: flex; - } - .chat-tools-group:first-child { - flex: 1; - } - .chat-tool-btn { - width: 2.25em; - height: 2.25em; - cursor: pointer; - color: $color-chat-disabled; - position: relative; - text-decoration: none; - display: inline-block; - border: 0.35em solid transparent; /* used for "padding" */ - .btn-icon { - opacity: 0.25; - transition: opacity 150ms; - width: 100%; - height: 100%; - display: inline-block; - } - } - .chat-tool-btn.ping .btn-icon { - animation: whisper-pulse 1.5s ease-in-out; - } - .chat-tool-btn:hover .btn-icon, - .chat-tool-btn.active .btn-icon { - opacity: 1; - } - #chat-emoticon-btn .btn-icon { - background: transparent url('../img/icon-emotes.svg') no-repeat center - center; - background-size: contain; - } - #chat-whisper-btn .btn-icon { - background: transparent url('../img/icon-whispers.svg') no-repeat center - center; - background-size: contain; - } - #chat-watching-focus-btn .btn-icon { - background: transparent url('../img/icon-watching.svg') no-repeat center - center; - background-size: contain; - } - #chat-settings-btn .btn-icon { - background: transparent url('../img/icon-settings.svg') no-repeat center - center; - background-size: contain; - } - #chat-users-btn .btn-icon { - background: transparent url('../img/icon-users.svg') no-repeat center center; - background-size: contain; - } -} - -#chat-input-control { - max-height: 140px; - position: relative; - color: $color-chat-text2; - background: $color-surface-dark2; - border: 1px solid $color-surface-dark3; - outline: none; - resize: none; - margin: 0; - width: 100%; - padding: $gutter-md; - border-radius: $bradius; - box-shadow: none; - box-sizing: border-box; - display: block; - overflow: hidden; -} -#chat-input-control::placeholder { - color: $color-chat-place; -} -#chat-input-subonly { - cursor: default; - display: none; - position: absolute; - top: $gutter-md; - right: $gutter-sm; - width: 1.5em; - height: 1.5em; - .btn-icon { - display: inline-block; - opacity: 0.75; - transition: opacity 150ms; - width: 100%; - height: 100%; - background: transparent url('../img/icon-subonly.svg') no-repeat center - center; - background-size: contain; - &:hover { - opacity: 1; - } - } -} - -.chat-lines { - font-family: $chat-lines-font; - line-height: 1.65em; - outline: 0 !important; -} - -/* Toolbar */ -#chat-whisper-unread-indicator { - left: 100%; - color: $color-light; - font-size: 0.75em; - position: absolute; - vertical-align: text-bottom; - margin-left: 0.5em; - top: 6px; -} - -/* Base ChatMessage */ -.msg-chat { - word-wrap: break-word; - padding: $gutter-xs $gutter-md * 2 $gutter-xs $gutter-md; - color: $color-chat-text2; - .time { - font-size: 0.8em; - color: $color-chat-disabled; - padding-right: $gutter-sm; - display: none; /* hidden by default */ - } - .user { - font-weight: 500; - cursor: pointer; - margin-left: $gutter-sm; - color: $color-label-user; - } - .chat-user { - cursor: pointer; - position: relative; - } - .chat-user:hover { - text-decoration: underline; - } - .greentext { - color: $color-green-text; - } - .sus { - text-transform: uppercase; - font-family: 'Among Us', $chat-lines-font; - } - .externallink { - border-style: solid; - border-color: transparent; - border-width: 1px 0 1px 0; - } - .externallink { - color: $color-link; - position: relative; - } - .externallink:visited { - color: $color-link-visited; - } - .externallink:hover, - .externallink:focus { - color: $color-link-hover; - } - .nsfw-link { - border-style: dashed; - border-color: transparent transparent $color-underline-nsfw transparent; - border-width: 1px 0 1px 0; - } - .nsfl-link { - border-style: dashed; - border-color: transparent transparent $color-underline-nsfl transparent; - border-width: 1px 0 1px 0; - } - .spoilers-link { - border-style: dashed; - border-color: transparent transparent $color-underline-spoilers transparent; - border-width: 1px 0 1px 0; - } - .hidden { - display: none !important; - } - - .embed-button { - position: relative; - top: 2.5px; - margin-left: $gutter-sm; - margin-right: $gutter-xs; - opacity: 0.5; - &:hover { - opacity: 1; - } - } -} - -/* Base ChatUserMessage */ -.msg-user .features { - display: inline-flex; - vertical-align: text-top; - align-items: center; - margin-left: -$gutter-xs; - .flair { - cursor: pointer; - margin-left: $gutter-xs; - } -} - -/* Status messages */ -.msg-status, -.msg-command, -.msg-error, -.msg-info { - white-space: pre-wrap; -} -.msg-status, -.msg-command, -.msg-error { - color: $color-chat-text1; -} -.msg-status .text:before, -.msg-command .text:before, -.msg-info .text:before, -.msg-error .text:before { - content: ''; - display: inline-block; - vertical-align: text-top; - margin-right: $gutter-sm; -} -.msg-status .text:before { - @extend .icon-status; -} -.msg-command .text:before { - @extend .icon-command; -} -.msg-info .text:before { - @extend .icon-info; -} -.msg-error .text:before { - @extend .icon-error; -} - -/* Own messages */ -.msg-own { - background-color: $color-chat-emphasize; - color: $color-chat-text1; -} - -/* Censored */ -.censored { - .text { - display: none; - } - .ctrl { - display: inline-block; - } - .ctrl:after { - color: $color-accent; - cursor: pointer; - padding-left: $gutter-md; - content: ''; - } - .ctrl:hover:after { - color: $color-accent-light; - } -} - -/* Continue */ -.msg-continue .text:before { - color: $color-chat-disabled; - content: '> '; -} -.msg-continue .features, -.msg-continue .user { - display: none; -} - -.msg-status.msg-continue .text:before, -.msg-command.msg-continue .text:before, -.msg-info.msg-continue .text:before, -.msg-error.msg-continue .text:before, -.msg-broadcast.msg-continue .text:before { - content: ''; -} - -/* /ME message */ -.msg-me .text { - font-style: italic; -} - -/* Whispers */ -.msg-whisper { - background-color: $color-chat-emphasize; -} -.msg-whisper a.user { - background: $color-accent; - padding: $gutter-xs $gutter-md $gutter-xs $gutter-md; - border-radius: $bradius; - font-weight: normal; - color: white; -} -.msg-whisper a.user:hover { - text-decoration: none; - color: $color-accent; - background: white; -} - -/* Tagging */ -.msg-tagged { - position: relative; -} -.pref-taggedvisibility .msg-tagged { - background-color: $color-chat-tagged; -} -.msg-tagged:before { - position: absolute; - top: 0; - left: 0; - bottom: 0; - min-width: 0.45em; - display: block; - background: $color-black; - content: ''; -} -.msg-tagged-green:before { - background-color: $color-green; -} -.msg-tagged-yellow:before { - background-color: $color-yellow; -} -.msg-tagged-orange:before { - background-color: $color-orange; -} -.msg-tagged-red:before { - background-color: $color-red; -} -.msg-tagged-purple:before { - background-color: $color-purple; -} -.msg-tagged-blue:before { - background-color: $color-blue; -} -.msg-tagged-sky:before { - background-color: $color-sky; -} -.msg-tagged-lime:before { - background-color: $color-lime; -} -.msg-tagged-pink:before { - background-color: $color-pink; -} -.msg-tagged-black:before { - background-color: $color-black; -} - -/* Historical messages */ -.msg-historical { - color: $text-color1; -} -.msg-historical:before { - background: none !important; -} -.msg-historical .time { - display: inline !important; -} -.chat-output:not(.chat-win-main) { - .msg-historical { - background-color: $color-chat-emphasize; - color: $color-chat-text1; - } - .msg-historical.msg-own { - background: none !important; - color: $color-chat-text2; - } -} - -.msg-donation, -.msg-subscription, -.msg-giftsub, -.msg-massgift, -.msg-broadcast { - text-shadow: 1px 1px 3px rgba(0, 0, 0, 1); - font-size: 1.1em; - font-weight: 400; - padding: unset; - border-radius: 10px; - border-style: solid; - border-color: unset; - border-width: 3px; - display: flex; - flex-direction: column; - margin: $gutter-sm $gutter-md * 2 $gutter-sm $gutter-sm; - - .event-wrapper { - border-radius: 10px; - } - - .event-top { - display: flex; - padding: $gutter-xs $gutter-md * 2 $gutter-xs $gutter-md; - background-color: $color-chat-emphasize; - justify-content: space-between; - align-items: center; - border-top-right-radius: 10px; - border-top-left-radius: 10px; - } - - .event-icon { - width: 2.25em; - height: 2.25em; - color: $color-chat-disabled; - position: relative; - text-decoration: none; - display: inline-block; - border: 0.25em solid transparent; - flex-shrink: 0; - opacity: 0.75; - } - - .event-bottom { - padding: $gutter-xs $gutter-md * 2 $gutter-xs $gutter-md; - background-color: $color-chat-bg; - border-bottom-right-radius: 10px; - border-bottom-left-radius: 10px; - } - - &:not(:has(.event-bottom)) { - .event-top { - border-bottom-right-radius: 10px; - border-bottom-left-radius: 10px; - } - } - - .user { - margin-left: unset; - text-shadow: none; - } - - &.censored { - .text { - display: none; - } - .event-bottom:after { - color: $color-accent; - cursor: pointer; - content: ''; - } - .event-bottom:hover:after { - color: $color-accent-light; - } - } -} - -/* Broadcasts */ -.msg-broadcast { - border-color: $color-text-broadcast; - - .broadcast-icon { - background: transparent url('../img/icon-broadcast.png') no-repeat center - center; - background-size: contain; - filter: invert(100%); - } -} - -/* Donations */ -.msg-donation { - .donation-icon { - &.amount-5 { - background: transparent url('../img/donation-amount-5.png') no-repeat - center center; - background-size: contain; - filter: invert(100%); - } - &.amount-10 { - background: transparent url('../img/donation-amount-10.png') no-repeat - center center; - background-size: contain; - filter: invert(100%); - } - &.amount-25 { - background: transparent url('../img/donation-amount-25.png') no-repeat - center center; - background-size: contain; - filter: invert(100%); - } - &.amount-50 { - background: transparent url('../img/donation-amount-50.png') no-repeat - center center; - background-size: contain; - filter: invert(100%); - } - &.amount-100 { - background: transparent url('../img/donation-amount-100.png') no-repeat - center center; - background-size: contain; - filter: invert(100%); - } - } - - &.amount-0 { - border-color: #1c5cdb; - } - &.amount-5 { - border-color: #59aeea; - } - &.amount-10 { - border-color: #2addc8; - } - &.amount-25 { - border-color: #4db524; - } - &.amount-50 { - border-color: #dd29d2; - } - &.amount-100 { - border-color: #e62117; - } -} - -/* Subscriptions */ -.msg-subscription, -.msg-giftsub, -.msg-massgift { - position: relative; - - .subscription-icon { - &.regular { - background: transparent url('../img/sub-regular.svg') no-repeat center - center; - background-size: contain; - } - &.gift { - background: transparent url('../img/sub-gift.png') no-repeat center center; - background-size: contain; - filter: invert(100%); - } - &.mass-gift { - background: transparent url('../img/sub-mass-gift.png') no-repeat center - center; - background-size: contain; - filter: invert(100%); - } - } - - .streak { - display: flex; - flex-direction: column; - width: calc(100% - 2.25em - $gutter-md * 2); - } - - .streak-message { - border-top-style: solid; - border-top-width: 1px; - border-top-color: $color-chat-place; - padding-top: 2px; - margin-top: 2px; - } - - .tier { - font-weight: 500; - cursor: unset; - - &:hover { - text-decoration: unset; - &:before { - content: unset !important; - } - } - } - - &.rainbow-border { - background-clip: padding-box; - border: solid 3px transparent; - border-radius: 10px; - - &:before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - margin: -3px; - border-radius: inherit; - background: var(--rainbow-gradient); - } - } -} - -.msg-death { - .text { - font-style: italic; - } - - .user:before { - content: ''; - display: inline-block; - vertical-align: text-top; - margin-right: $gutter-sm; - - @extend .icon-command; - } -} - -/* Highlight */ -.msg-highlight { - color: $color-chat-text1; - background-color: $color-chat-highlight; - time, - .continue { - color: $color-chat-text3; - } -} -.pref-taggedvisibility .msg-highlight { - background-color: $color-chat-highlight; -} - -/* Pinned Messages */ -#chat-pinned-frame { - display: none; - padding: 4px; - position: absolute; - width: 100%; - z-index: 210; - - &.active { - display: block; - } +$num-size-map: ( + 1: 14px, + 2: 15px, + 3: 16px, + 4: 18px, + 5: 20px, + 6: 22px, + 7: 24px, + 8: 26px, + 9: 32px, + 10: 52px, +); - #chat-pinned-show-btn { - display: none; - width: 1.3em; - height: 1.3em; - cursor: pointer; - color: $color-chat-disabled; - position: absolute; - text-decoration: none; - right: 0; - margin-top: 2px; - margin-right: $gutter-md; - &.active { - display: block; - } - .btn-icon { - opacity: 0.25; - transition: opacity 150ms; - width: 100%; - height: 100%; - display: inline-block; - background: transparent url('../img/icon-pin.svg') no-repeat center center; - background-size: contain; - } - &:hover .btn-icon { - opacity: 1; +.onstreamchat { + @each $num, $size in $num-size-map { + body.pref-fontscale[data-fontscale='#{$num}'] & { + font-size: $size; } } } - -.msg-pinned { - background-color: $color-chat-emphasize; - padding-top: $gutter-md; - padding-bottom: $gutter-md; - border-style: solid; - border-radius: 10px; - border-color: #505050; - transition: all 0.15s ease-in-out; - - &:hover { - border-color: $color-chat-text3; - } - - .user { - text-shadow: none; - } - - .chat-tool-btn { - width: 1.5em; - height: 1.5em; - cursor: pointer; - color: $color-chat-disabled; - position: relative; - text-decoration: none; - float: right; - .btn-icon { - opacity: 0.25; - transition: opacity 150ms; - width: 100%; - height: 100%; - display: inline-block; - } - } - .chat-tool-btn:hover .btn-icon, - .chat-tool-btn.active .btn-icon { - opacity: 1; - } - - #close-pin-btn .btn-icon { - background: transparent url('../img/icon-close.svg') no-repeat center center; - background-size: contain; - } - #unpin-btn .btn-icon { - background: transparent url('../img/icon-unpin.svg') no-repeat center center; - background-size: contain; - } - - &.hidden { - display: none; - } -} - -.watching-focus:not(.focus) { - .msg-emote, - .msg-user, - .msg-death { - opacity: 0.3; - - &.watching-same, - &.msg-highlight { - opacity: 1; - } - } -} - -/* Focus or highlight a line */ -.focus .msg-chat { - opacity: 0.3; - - &.msg-pinned { - opacity: 1; - } -} - -/* Emotes and combo */ -@keyframes emote-hit { - 0% { - color: #b91010; - font-size: 200%; - } - 1% { - color: $color-light; - font-size: 190%; - } - 2% { - color: #b91010; - font-size: 200%; - } - 3% { - color: $color-light; - font-size: 190%; - } - 4% { - color: #b91010; - font-size: 200%; - } - 100% { - color: $color-light; - font-size: 120%; - } -} -@keyframes emote-complete { - 0% { - transform: translate(-10px, 0); - opacity: 0; - } - 2% { - transform: translate(10px, 0); - color: white; - opacity: 1; - } - 100% { - transform: translate(0, 0); - opacity: 1; - } -} -@keyframes emote-greyout { - 0% { - filter: grayscale(0); - } - 75% { - filter: grayscale(0); - } - 100% { - filter: grayscale(100%); - } -} -.emote { - display: inline-block; - position: relative; - overflow: hidden; - text-indent: -999em; -} -.chat-combo { - color: $color-chat-text1; - display: inline-block; - position: relative; - line-height: 2em; - padding-left: $gutter-md; - i { - font-style: normal; - display: inline-block; - vertical-align: middle; - } - .hit, - .combo, - .count, - .x { - text-shadow: - -1px -1px 0 #000, - 1px -1px 0 #000, - -1px 1px 0 #000, - 1px 1px 0 #000; - } - .combo { - display: none; - color: $text-color1; - font-weight: normal; - } - .hit { - color: $color-light; - animation: emote-hit 600ms 1; - font-style: italic; - text-transform: uppercase; - margin-left: 0; - letter-spacing: 0.05em; - display: inline-block; - } - .count { - font-size: 1.15em; - letter-spacing: -0.05em; - font-weight: bold; - } - &.x5 { - .count { - font-size: 150%; - } - } - &.x10 { - .count { - font-size: 200%; - } - .combo { - font-weight: bold; - } - } - &.x20 { - .count { - font-size: 300%; - } - .combo { - font-weight: bold; - } - } - &.x30 { - .count { - font-size: 400%; - } - .combo { - font-weight: bold; - } - } - &.x50 { - .count { - font-size: 500%; - } - .combo { - font-weight: bold; - } - } - &.combo-complete { - &.x10, - &.x20, - &.x30, - &.x50 { - animation: emote-greyout 3500ms 1; - background: transparent url('../img/emote-splat.png') no-repeat center - center; - background-size: 100% 100%; - filter: grayscale(100%); - .combo { - color: $color-light; - margin-left: $gutter-md; - } - } - .combo { - animation: emote-complete 1000ms 1; - display: inline-block; - } - .hit { - display: none; - } - } -} - -/* Helpers */ -.chat-scroll-notify { - padding: 0.25em 0; - color: $color-chat-text3; - background: $color-surface-dark3; - position: absolute; - bottom: -2.5em; - left: $gutter-md; - right: $gutter-md; - text-align: center; - cursor: pointer; - z-index: 130; - border-radius: $bradius; - opacity: 0; - transition: all 250ms; - transition-timing-function: cubic-bezier(0.6, 0.08, 0.99, 0.54); -} -.chat-scroll-notify:hover { - color: $color-chat-text1; -} -.chat-unpinned .chat-scroll-notify { - transition-timing-function: cubic-bezier(0, 0.99, 0.18, 0.99); - opacity: 1; - bottom: 0; -} - -#chat.chat-autocomplete-in { - .chat-scroll-notify { - display: none !important; - } - #chat-input-control { - border-radius: 0 0 $bradius $bradius; - } -} - -#chat:not(.pref-autocompletehelper) { - #chat-auto-complete { - display: none !important; - } -} - -/* Auto complete */ -#chat-auto-complete { - pointer-events: none; - transition: opacity 750ms; - transition-timing-function: cubic-bezier(0, 0.74, 0.1, 0.99); - opacity: 0; - z-index: 131; - border-radius: $bradius $bradius 0 0; - position: absolute; - font-size: 1.1em; - line-height: 2em; - height: 2em; - top: -2em; - left: 0; - right: 0; - overflow: hidden; - white-space: nowrap; - &.active { - opacity: 1; - pointer-events: auto; - } - ul { - position: absolute; - white-space: nowrap; - list-style: none; - padding: 0; - margin: 0; - } - li { - padding: 0 $gutter-sm; - cursor: pointer; - text-decoration: none; - display: inline-block; - color: $color-chat-text3; - background: rgba($color-surface-dark1, 0.75); - &:first-child { - border-radius: $bradius 0 0 0; - } - &:last-child { - border-radius: 0 $bradius 0 0; - } - &:hover { - color: lighten($color-chat-text3, 20); - } - &.active { - color: $text-color; - } - } -} - -/* Window selector */ -#chat-windows-select { - padding-top: 0.5em; - background: $color-surface-dark4; - white-space: nowrap; - display: flex; - z-index: 100; - .tab { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - border-radius: $bradius $bradius 0 0; - color: $color-chat-text3; - user-select: none; - cursor: pointer; - font-size: 1em; - padding: $gutter-sm $gutter-lg; - } - .tab-close { - background: transparent url('../img/icon-close.svg') no-repeat center center; - background-size: contain; - width: 1em; - height: 1em; - opacity: 0.25; - margin-left: $gutter-md; - display: none; - &:hover { - opacity: 1; - } - } - .tab:hover, - .tab.unread { - color: $color-chat-text1; - } - .tab.active { - color: $color-chat-text1; - background: $color-surface-dark1; - margin-left: 0; - margin-right: 0; - overflow: visible; - text-overflow: unset; - min-width: auto; - display: flex; - align-items: center; - .tab-close { - display: inline-block; - } - } - .win-main { - width: 3em; - flex-shrink: 0; - .tab-close { - display: none !important; - } - span { - display: none; - } - .dgg-icon { - background: transparent url('../img/dgg-icon.svg') no-repeat center center; - background-size: contain; - display: block; - width: 100%; - height: 100%; - } - } -} - -/* Chat menus */ -.chat-menu { - display: none; - position: absolute; - z-index: 220; - height: auto; - bottom: 6.3em; - max-width: 75%; - min-width: $chat-min-width; - width: 33.3333%; - right: 0; - top: 0; - .toolbar { - position: relative; - border-bottom: 1px solid $color-surface-dark4; - top: auto; - left: auto; - h5 { - justify-content: space-between; - padding: $gutter-md; - font-size: 1.1em; - font-weight: normal; - color: $text-color1; - align-items: center; - user-select: none; - display: flex; - margin: 0; - } - span { - line-height: 1.25em; - display: inline-block; - } - .chat-menu-close { - width: 1em; - height: 1em; - opacity: 0.5; - font-size: 1.2em; - display: inline-block; - background: transparent url('../img/icon-close.svg') no-repeat center - center; - background-size: contain; - cursor: pointer; - &:hover { - opacity: 1; - } - } - } - .floating-window { - height: fit-content !important; - } - .chat-menu-inner { - height: 100%; - display: flex; - flex-direction: column; - background-color: $color-surface-dark3; - } - .scrollable { - flex: 1; - height: 100%; - } - &.active { - display: block; - } - &.left { - left: 0; - right: auto; - .chat-menu-inner { - border-bottom-right-radius: $bradius; - } - } - &.right { - right: 0; - left: auto; - .chat-menu-inner { - border-bottom-left-radius: $bradius; - } - } -} - -#chat-emote-list { - .lock { - width: 1em; - height: 1em; - opacity: 0.5; - display: inline-flex; - margin-right: $gutter-sm; - background: transparent url('../img/icon-lock.svg') no-repeat center center; - background-size: contain; - } - .content { - text-align: center; - outline: 0; - .emote-group { - display: flex; - flex-wrap: wrap; - justify-content: center; - margin: $gutter-lg; - .emote-item { - user-select: none; - padding: $gutter-sm; - margin: auto; - .emote { - cursor: pointer; - &.disabled { - cursor: not-allowed; - opacity: 0.5; - } - } - } - } - } - input { - padding: $gutter-lg $gutter-lg; - border: none; - background: none; - border-radius: 0; - } - .favorite { - flex: 0 88px; - border-top: 1px solid $color-surface-dark4; - } -} - -#chat-whisper-users { - .content > ul { - margin: 0; - padding: $gutter-md 0; - } - .conversation { - list-style: none; - position: relative; - cursor: pointer; - padding-left: $gutter-md; - display: flex; - align-items: center; - .user { - color: $color-accent; - display: block; - } - .user:hover { - color: $color-accent-light; - .badge { - color: $color-chat-text1; - } - } - .badge { - font-size: 0.8em; - padding: 0 $gutter-md; - display: inline-block; - background: $color-surface-dark4; - border-radius: 50%; - margin: $gutter-xs 0; - color: $color-chat-text2; - } - .badge, - .remove { - float: right; - margin-right: $gutter-md; - } - .remove { - opacity: 0.25; - background: transparent url(../img/icon-close.svg) no-repeat center center; - background-size: contain; - display: inline-block; - text-decoration: none; - text-align: center; - line-height: 1.3em; - width: 1.3em; - height: 1.3em; - } - .remove:hover { - opacity: 1; - } - } - .unread-0 .badge { - display: none; - } - .unread-0 .user, - .unread-0 .user:hover { - color: $text-color1; - } - .empty { - color: $text-color1; - margin: $gutter-lg; - } -} - -#chat-user-list { - .section { - margin: 1.5em 0em 0.5em; - .title { - padding: 0em 0em 0.2em 1em; - font-weight: 600; - border-bottom: 1px solid; - .features { - float: right; - display: inline-flex; - vertical-align: text-top; - align-items: center; - margin-right: $gutter-md; - .flair { - cursor: pointer; - margin-right: $gutter-xs; - } - } - } - .user-entry { - margin: 0; - cursor: pointer; - padding: 0 $gutter-lg; - text-decoration: none; - visibility: visible; - align-items: center; - display: flex; - .user-actions { - position: absolute; - right: $gutter-md; - .mention-nick { - background: transparent url('../img/icon-share.svg') no-repeat center - center; - background-size: contain; - } - .whisper-nick { - background: transparent url('../img/icon-whisper.svg') no-repeat - center center; - background-size: contain; - } - .mention-nick, - .whisper-nick { - visibility: hidden; - margin-right: $gutter-xs; - margin-top: 0.45em; - display: inline-block; - opacity: 0.25; - width: 1em; - height: 1em; - &:hover { - opacity: 1; - } - } - } - &:hover .mention-nick, - &:hover .whisper-nick { - visibility: visible; - } - &:hover { - background: #282828; - } - } - } - .content { - margin-top: -$gutter-md; - padding-bottom: $gutter-lg; - } - .scrollable { - max-height: calc(100% - 3em); - } - &.search-in .user-entry { - display: none; - } - &.search-in .user-entry.found { - display: flex; - } - input { - padding: $gutter-lg $gutter-lg; - border: none; - background: none; - border-radius: 0; - } -} - -#chat-emote-tooltip { - height: fit-content; - width: fit-content; - min-width: 75px; - max-width: 250px; - z-index: 221; - .emote-image { - display: flex; - justify-content: center; - padding: $gutter-md; - } - .emote-info { - border-top: 1px solid $color-surface-dark4; - padding: $gutter-sm; - div { - display: flex; - justify-content: center; - &.name { - font-weight: 500; - color: $text-color; - } - &.tier, - &.creator { - color: $text-color1; - } - } - button.favorite { - width: 100%; - display: flex; - justify-content: center; - margin-top: 5px; - .btn-icon { - transition: background-color 500ms; - height: 20px; - aspect-ratio: 1; - mask: url('../img/icon-favorite.svg'); - background-color: $text-color2; - } - &.favorited .btn-icon { - mask: url('../img/icon-favorite-fill.svg'); - } - &:hover .btn-icon { - background-color: $color-yellow; - } - } - } -} - -#chat-user-info { - height: fit-content; - bottom: unset; - min-width: 250px; - max-width: 350px; - .floating-window { - box-shadow: 0px 0px 6px black; - border-radius: 6px; - } - .user-info h5 { - padding-left: $gutter-md; - margin-top: unset; - margin-bottom: unset; - border-bottom: 1px solid $color-surface-dark4; - } - .flairs { - margin-top: $gutter-lg; - margin-bottom: $gutter-lg; - padding-left: $gutter-md; - } - .features { - display: grid; - grid-template-columns: repeat(3, 1fr); - align-items: center; - justify-items: center; - margin-left: -0.2em; - } - .stalk { - height: 100px; - } - .stalk .scrollable { - background: $color-chat-bg; - } - .action-buttons { - margin: $gutter-md 0 $gutter-md 0; - display: grid; - grid-auto-flow: column; - justify-content: space-around; - } - .chat-tool-btn { - width: 2.25em; - height: 2.25em; - cursor: pointer; - color: $color-chat-disabled; - position: relative; - text-decoration: none; - display: inline-block; - border: 0.35em solid transparent; /* used for "padding" */ - .btn-icon { - opacity: 0.25; - transition: opacity 150ms; - width: 100%; - height: 100%; - display: inline-block; - } - } - .chat-tool-btn:hover .btn-icon, - .chat-tool-btn.active .btn-icon { - opacity: 1; - } - #mute-user-btn .btn-icon { - background: transparent url('../img/icon-mute.svg') no-repeat center center; - background-size: contain; - } - #ban-user-btn .btn-icon { - background: transparent url('../img/icon-ban.svg') no-repeat center center; - background-size: contain; - } - #logs-user-btn .btn-icon { - background: transparent url('../img/icon-logs.svg') no-repeat center center; - background-size: contain; - } - #whisper-user-btn .btn-icon { - background: transparent url('../img/icon-whispers.svg') no-repeat center - center; - background-size: contain; - } - #ignore-user-btn .btn-icon { - background: transparent url('../img/icon-ignore.svg') no-repeat center - center; - background-size: contain; - } - #unignore-user-btn .btn-icon { - background: transparent url('../img/icon-unignore.svg') no-repeat center - center; - background-size: contain; - } - #action-durations { - height: calc(20px + 2 * $gutter-md); - display: flex; - justify-content: space-around; - text-align: center; - font-size: 1.2em; - } - .ban-duration-button, - .mute-duration-button { - opacity: 0.25; - transition: opacity 150ms; - color: white; - width: 100%; - } - .ban-duration-button:hover, - .mute-duration-button:hover { - opacity: 1; - } - .hidden { - display: none !important; - } -} - -#chat-settings { - #chat-settings-form { - margin: $gutter-lg 0; - } - - .form-group { - margin: $gutter-lg $gutter-lg; - display: block; - position: relative; - } - - h4 { - font-size: 0.9em; - margin-top: $gutter-lg * 2; - margin-bottom: $gutter-lg; - padding-left: $gutter-lg; - color: $text-color-disabled; - text-transform: uppercase; - font-weight: 600; - } - label { - display: inline-block; - font-weight: normal; - max-width: 100%; - margin-bottom: $gutter-md; - } - .checkbox label { - margin-bottom: 0; - font-weight: 400; - max-width: 100%; - cursor: pointer; - display: flex; - justify-items: center; - align-items: center; - } - .checkbox input { - margin: 0 $gutter-sm 0 0; - line-height: normal; - box-sizing: border-box; - padding: 0; - } - select { - border-radius: $bradius; - padding: $gutter-sm; - width: 100%; - } -} - -/* Login screen */ -#chat-login-screen { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgba($color-surface-dark1, 0.75); - position: absolute; - text-align: center; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 300; - h2 { - font-size: 2.5em; - font-weight: normal; - margin: 0; - } - p { - margin: 1em 0 1.5em 0; - } -} - -/* Loading screen */ -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -#chat-loading { - opacity: 0.2; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - .chat-loading-icon { - width: 4em; - height: 4em; - display: inline-block; - animation: spin 2s linear infinite; - background: transparent url('../img/icon-settings.svg') no-repeat center - center; - background-size: contain; - } -} - -/* Settings */ -.pref-showtime .time { - display: inline; -} -.pref-hideflairicons .features { - display: none; -} -.pref-autocompletehelper #chat-auto-complete.active { - display: block; -} -.pref-disableanimations { - .emote, - .emote:hover, - .emote::before, - .emote::after { - animation: none !important; - } - - a.user, - span.user { - animation: none !important; - } -} - -/* OverlayScrollbars theme */ -.dgg-scroller-theme.os-scrollbar-horizontal { - right: 10px; - height: 10px; -} - -.dgg-scroller-theme.os-scrollbar-vertical { - bottom: 10px; - width: 10px; -} - -.dgg-scroller-theme.os-scrollbar-rtl.os-scrollbar-horizontal { - left: 10px; - right: 0; -} - -.dgg-scroller-theme.os-scrollbar { - padding: 2px; - box-sizing: border-box; - background: transparent; -} - -.dgg-scroller-theme.os-scrollbar-unusable { - background: transparent; -} - -.dgg-scroller-theme.os-scrollbar > .os-scrollbar-track { - background: transparent; -} - -.dgg-scroller-theme.os-scrollbar-horizontal - > .os-scrollbar-track - > .os-scrollbar-handle { - min-width: 30px; -} - -.dgg-scroller-theme.os-scrollbar-vertical - > .os-scrollbar-track - > .os-scrollbar-handle { - min-height: 30px; -} - -.dgg-scroller-theme.os-scrollbar-transition - > .os-scrollbar-track - > .os-scrollbar-handle { - transition: background-color 0.3s; -} - -.dgg-scroller-theme.os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle { - background: rgba(255, 255, 255, 0.4); -} - -.dgg-scroller-theme.os-scrollbar:hover - > .os-scrollbar-track - > .os-scrollbar-handle { - background: rgba(255, 255, 255, 0.55); -} - -.dgg-scroller-theme.os-scrollbar - > .os-scrollbar-track - > .os-scrollbar-handle.active { - background: rgba(255, 255, 255, 0.7); -} - -.dgg-scroller-theme.os-scrollbar-horizontal .os-scrollbar-handle:before, -.dgg-scroller-theme.os-scrollbar-vertical .os-scrollbar-handle:before { - content: ''; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - display: block; -} - -.dgg-scroller-theme.os-scrollbar-horizontal .os-scrollbar-handle:before { - top: -6px; - bottom: -2px; -} - -.dgg-scroller-theme.os-scrollbar-vertical .os-scrollbar-handle:before { - left: -6px; - right: -2px; -} - -.dgg-scroller-theme.os-scrollbar-rtl.os-scrollbar-vertical - .os-scrollbar-handle:before { - right: -6px; - left: -2px; -} - -/** Form Controls */ -.form-control { - color: $color-chat-text2; - background: $color-surface-dark1; - border: 1px solid lighten($color-surface-dark1, 9); -} -.form-control:focus { - color: $color-chat-text1; - background: $color-surface-dark1; - border: 1px solid lighten($color-surface-dark1, 15); - box-shadow: none; - outline: none; -} -.form-control[disabled], -.form-control[readonly], -.form-control::placeholder { - color: $color-chat-place; -} -.form-control[type='text'], -textarea.form-control { - width: 100%; -} - -button.btn { - display: inline-block; - padding: $gutter-md $gutter-lg; - margin-bottom: 0; - font-weight: normal; - text-align: center; - white-space: nowrap; - vertical-align: middle; - touch-action: manipulation; - cursor: pointer; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: $bradius; -} -.btn-primary { - color: $text-color; - background-color: $color-accent; - border-color: $color-accent; -} -.btn-default { - color: $text-color3; - background-color: $color-surface-light1; - border-color: $color-surface-light3; -} - -@keyframes msg-slide-in { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.onstreamchat { - /*background: #434343 !important;*/ - - .chat-lines { - font-size: 1.5em; - } - - #chat, - .msg-chat, - .msg-chat:not(.rainbow-border):before, - .greentext { - background: none !important; - } - .chat-combo { - background-image: none !important; - } - .chat-lines { - font-family: 'Roboto', Helvetica, 'Trebuchet MS', Verdana, sans-serif; - font-weight: 500 !important; - } - #chat-output-frame { - margin: 0; - } - #chat-pinned-frame { - display: none !important; - } - - // Donations, subs and broadcasts - .msg-donation, - .msg-subscription, - .msg-giftsub, - .msg-massgift, - .msg-broadcast { - text-shadow: none; - padding: 0; - border-style: none none none solid; - border-width: 6px; - border-radius: 0; - - .event-top, - .event-bottom { - padding: 0 0 0 $gutter-md; - background: none; - } - - &.rainbow-border { - &:before { - content: ''; - position: absolute; - top: unset; - right: 100%; - bottom: unset; - left: unset; - z-index: -1; - margin: -1px 0 0 0; - border-radius: inherit; - background: var(--rainbow-gradient); - aspect-ratio: 1/1; - height: 100%; - transform: rotate(90deg); - } - } - } - - time, - .time, - .features, - .msg-ui, - .msg-info, - .msg-chat .text:before { - display: none !important; - } - - .continue, - .os-scrollbar { - visibility: hidden !important; - } - - .chat-lines, - .msg-chat, - .greentext, - .combo { - color: $text-color; - } - - .msg-pinned { - position: unset; - text-shadow: unset; - background-color: unset; - font-size: unset; - font-weight: unset; - padding-top: unset; - padding-bottom: unset; - box-shadow: unset; - border-radius: unset; - - .chat-tool-btn { - display: none !important; - } - } - - .msg-chat { - margin: 0; - padding: 1px 20px 1px 5px; - filter: drop-shadow(-1px -1px 0 rgba(black, 0.5)) - drop-shadow(1px -1px 0 rgba(black, 0.5)) - drop-shadow(-1px 1px 0 rgba(black, 0.5)) - drop-shadow(1px 1px 0 rgba(black, 0.5)); - letter-spacing: 0.03em; - } - - .msg-user, - .msg-status { - animation: msg-slide-in 0.15s cubic-bezier(0.72, 0.03, 0.45, 1); - } - - a.externallink, - a.externallink:hover { - color: $color-accent-light; - } -} - -body.pref-fontscale[data-fontscale='1'] { - .onstreamchat { - font-size: 14px; - } -} -body.pref-fontscale[data-fontscale='2'] { - .onstreamchat { - font-size: 15px; - } -} -body.pref-fontscale[data-fontscale='3'] { - .onstreamchat { - font-size: 16px; - } -} -body.pref-fontscale[data-fontscale='4'] { - .onstreamchat { - font-size: 18px; - } -} -body.pref-fontscale[data-fontscale='5'] { - .onstreamchat { - font-size: 20px; - } -} -body.pref-fontscale[data-fontscale='6'] { - .onstreamchat { - font-size: 22px; - } -} -body.pref-fontscale[data-fontscale='7'] { - .onstreamchat { - font-size: 24px; - } -} -body.pref-fontscale[data-fontscale='8'] { - .onstreamchat { - font-size: 26px; - } -} -body.pref-fontscale[data-fontscale='9'] { - .onstreamchat { - font-size: 32px; - } -} -body.pref-fontscale[data-fontscale='10'] { - .onstreamchat { - font-size: 52px; - } -} - -#chat-poll-frame { - display: none; - transform: translateY(-100%); - transition: transform 250ms ease-out; - &.active { - transform: translateY(0); - display: block; - } - .poll-frame { - padding: 0.5em 0.75em; - background: #2f2f2f; - border-radius: 4px; - margin: 4px; - color: $text-color; - .poll-header { - display: flex; - flex-direction: column; - width: 100%; - margin: 0.25em 0.1em 0.75em 0.1em; - .poll-top { - display: flex; - justify-content: space-between; - width: 100%; - .poll-question { - font-weight: 700; - } - .poll-close { - width: 2em; - height: 1em; - opacity: 0.5; - font-size: 1.2em; - background: transparent url('../img/icon-close.svg') no-repeat center - center; - background-size: contain; - cursor: pointer; - &:hover { - opacity: 1; - } - } - } - .poll-title { - display: flex; - justify-content: space-between; - width: 100%; - opacity: 0.5; - .poll-votes { - margin-right: $gutter-sm; - } - } - } - .opt { - display: block; - align-items: center; - margin-bottom: $gutter-md; - background-color: $color-surface-dark3; - border-radius: 6px; - padding: 4px; - .opt-info { - display: flex; - .opt-vote-number { - height: fit-content; - border-radius: 4px; - padding: 0 0.3em 0 0.3em; - margin: 0 0.3em 0.3em 0; - } - .opt-bar-option { - font-weight: 500; - font-size: 0.85em; - color: $text-color; - margin: 0.1em 0em 0.25em 0; - display: flex; - flex-grow: 1; - } - .opt-bar-value { - font-weight: 500; - font-size: 0.85em; - margin-right: $gutter-sm; - color: $text-color; - white-space: nowrap; - } - } - .opt-bar { - flex: 1; - overflow: hidden; - background-color: $color-surface-dark2; - border-radius: 4px; - max-height: 0.75em; - .opt-bar-inner { - padding: 0 0.25em; - min-height: 0.75em; - box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); - transition: all 750ms; - background-color: $color-darkred; - border-radius: 4px; - } - } - &.opt-marked .opt-info .opt-vote-number { - background-color: $color-darkred; - color: $text-color; - } - } - .poll-footer { - color: $text-color1; - margin: 0.75em 0 0.5em 0; - display: flex; - .poll-timer { - background-color: $color-surface-dark3; - overflow: hidden; - padding: 3px; - flex: 1; - border-radius: 6px; - .poll-timer-inner { - min-height: 0.3em; - box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); - background-color: $color-blue; - border-radius: 4px; - } - } - } - } - &:not(.poll-completed) { - .opt:hover { - cursor: pointer; - background-color: $color-surface-dark2; - } - } - &.poll-completed { - .opt:not(.opt-winner) { - .opt-bar-inner { - background-color: $color-surface-dark4 !important; - } - &.opt-marked { - .opt-info .opt-vote-number { - background-color: $color-surface-dark4; - } - } - } - .opt-winner { - .opt-bar-inner { - background-color: $color-darkgreen !important; - } - &.opt-marked { - .opt-info .opt-vote-number { - background-color: $color-darkgreen !important; - } - } - } - } -} - -#chat.onstreamchat #chat-poll-frame { - display: none !important; -} - -@font-face { - font-family: 'Among Us'; - src: url('../../../fonts/AmongUs-Regular.ttf') format('truetype'); -} - -.tippy-box[data-theme~='dgg'] { - text-align: center; - background-color: white; - color: #000; -} - -.tippy-box[data-theme~='dgg'] > .tippy-svg-arrow { - fill: white; -} diff --git a/assets/chat/img/icon-ellipsis-vertical.svg b/assets/chat/img/icon-ellipsis-vertical.svg new file mode 100644 index 00000000..1de61600 --- /dev/null +++ b/assets/chat/img/icon-ellipsis-vertical.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/assets/chat/js/chat.js b/assets/chat/js/chat.js index 84409056..1515ce57 100644 --- a/assets/chat/js/chat.js +++ b/assets/chat/js/chat.js @@ -31,7 +31,9 @@ import { ChatEmoteTooltip, ChatSettingsMenu, ChatUserInfoMenu, + ChatEventActionMenu, } from './menus'; +import ChatEventBar from './event-bar/EventBar'; import ChatAutoComplete from './autocomplete'; import ChatInputHistory from './history'; import ChatUserFocus from './focus'; @@ -56,6 +58,7 @@ import makeSafeForRegex, { import { HashLinkConverter, MISSING_ARG_ERROR } from './hashlinkconverter'; import ChatCommands from './commands'; import MessageTemplateHTML from '../../views/templates.html'; +import EventBarEvent from './event-bar/EventBarEvent'; class Chat { constructor(config) { @@ -151,6 +154,7 @@ class Chat { this.source.on('ADDPHRASE', (data) => this.onADDPHRASE(data)); this.source.on('REMOVEPHRASE', (data) => this.onREMOVEPHRASE(data)); this.source.on('DEATH', (data) => this.onDEATH(data)); + this.source.on('PAIDEVENTS', (data) => this.onPAIDEVENTS(data)); this.control.on('SEND', (data) => this.cmdSEND(data)); this.control.on('HINT', (data) => this.cmdHINT(data)); @@ -318,6 +322,11 @@ class Chat { this.mainwindow = new ChatWindow('main').into(this); this.mutedtimer = new MutedTimer(this); this.chatpoll = new ChatPoll(this); + + this.eventBar = new ChatEventBar(); + this.eventBar.on('eventSelected', () => this.onEVENTSELECTED()); + this.eventBar.on('eventUnselected', () => this.onEVENTUNSELECTED()); + this.pinnedMessage = null; this.windowToFront('main'); @@ -371,6 +380,18 @@ class Chat { ), ); + const eventActionMenu = new ChatEventActionMenu( + this.ui.find('#event-action-menu'), + this.ui.find('.msg-event .event-button'), + this, + ); + eventActionMenu.on('removeEvent', this.handleRemoveEvent.bind(this)); + eventActionMenu.on( + 'removeEvent', + this.eventBar.unselect.bind(this.eventBar), + ); + this.menus.set('event-action-menu', eventActionMenu); + this.autocomplete.bind(this); // Chat input @@ -416,7 +437,18 @@ class Chat { // ESC document.addEventListener('keydown', (e) => { - if (isKeyCode(e, KEYCODES.ESC)) ChatMenu.closeMenus(this); // ESC key + if (isKeyCode(e, KEYCODES.ESC)) { + const activeWindow = this.getActiveWindow(); + if (this.getActiveMenu()) { + ChatMenu.closeMenus(this); + } else if (this.eventBar.isEventSelected()) { + this.eventBar.unselect(); + } else if (!activeWindow.isScrollPinned()) { + activeWindow.scrollBottom(); + } else if (this.userfocus.isFocused()) { + this.userfocus.clearFocus(); + } + } }); // Visibility @@ -780,7 +812,15 @@ class Chat { win.addMessage(this, message); // Hide the message if the user is ignored - if (this.ignored(message.user?.username, message.message)) { + if ( + ![ + MessageTypes.UI, + MessageTypes.INFO, + MessageTypes.ERROR, + MessageTypes.STATUS, + ].includes(message.type) && + this.ignored(message.user?.username, message.message) + ) { message.ignore(); } @@ -895,6 +935,10 @@ class Chat { if (this.mainwindow !== null) this.mainwindow.update(); } + getActiveMenu() { + return [...this.menus.values()].find((menu) => menu.visible); + } + censor(nick) { for (const message of this.mainwindow.messages) { if (message.user?.username === nick.toLowerCase()) { @@ -946,7 +990,7 @@ class Chat { } this.settings.set('favoriteemotes', [...this.favoriteemotes]); this.applySettings(); - this.menus.get('emotes').buildFavoriteEmoteMenu(); + this.menus.get('emotes').buildEmoteMenu(); return !exists; } @@ -1014,9 +1058,10 @@ class Chat { if (data.recipient) { users.push(this.addUser(data.recipient)); } - users.forEach((u) => - this.autocomplete.add(u.displayName, false, Date.now()), - ); + users.forEach((u) => { + if (this.ignored(u.username)) return; + this.autocomplete.add(u.displayName, false, Date.now()); + }); } } @@ -1312,18 +1357,75 @@ class Chat { onSUBSCRIPTION(data) { MessageBuilder.subscription(data).into(this); + + // Don't add events when loading messages from history because the + // `PAIDEVENTS` payload will contain those events + if (!this.backlogloading) { + const eventBarEvent = new EventBarEvent( + this, + MessageTypes.SUBSCRIPTION, + data, + ); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } + } } onGIFTSUB(data) { MessageBuilder.gift(data).into(this); + + if (!this.backlogloading) { + const eventBarEvent = new EventBarEvent(this, MessageTypes.GIFTSUB, data); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } + } } onMASSGIFT(data) { MessageBuilder.massgift(data).into(this); + + if (!this.backlogloading) { + const eventBarEvent = new EventBarEvent( + this, + MessageTypes.MASSGIFT, + data, + ); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } + } } onDONATION(data) { MessageBuilder.donation(data).into(this); + + if (!this.backlogloading) { + const eventBarEvent = new EventBarEvent( + this, + MessageTypes.DONATION, + data, + ); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } + } + } + + onPAIDEVENTS(lines) { + const events = lines.map((l) => { + const { eventname, data } = this.source.parse({ data: l }); + return new EventBarEvent(this, eventname, data); + }); + this.eventBar.replaceEvents(events); + + this.mainwindow.update(); + this.eventBar.sort(); } onADDPHRASE(data) { @@ -1505,6 +1607,15 @@ class Chat { MessageBuilder.death(data.data, user, data.timestamp).into(this); } + onEVENTSELECTED() { + // Hide full pinned message interface to make everything look nice + if (this.pinnedMessage) this.pinnedMessage.hidden = true; + } + + onEVENTUNSELECTED() { + if (this.pinnedMessage) this.pinnedMessage.hidden = false; + } + cmdSHOWPOLL() { if (this.chatpoll.poll) { this.chatpoll.show(); @@ -1631,6 +1742,8 @@ class Chat { if (!failure) { validUsernames.forEach((username) => { this.ignore(username, true); + const user = this.users.get(username); + if (user) this.autocomplete.remove(user.displayName, true); }); const resultArray = Array.from(validUsernames.values()); const resultMessage = @@ -1661,6 +1774,8 @@ class Chat { if (!failure) { validUsernames.forEach((username) => { this.ignore(username, false); + const user = this.users.get(username); + if (user) this.autocomplete.add(user.displayName, false, Date.now()); }); const haveOrHas = parts.length === 1 ? 'has' : 'have'; MessageBuilder.status( @@ -2528,6 +2643,11 @@ class Chat { hostname = hostname.split('?')[0]; return hostname; } + + handleRemoveEvent(eventUuid) { + ChatMenu.closeMenus(this); + this.source.send('REMOVEEVENT', { data: eventUuid }); + } } export default Chat; diff --git a/assets/chat/js/const.js b/assets/chat/js/const.js index 7b518a4e..138a8004 100644 --- a/assets/chat/js/const.js +++ b/assets/chat/js/const.js @@ -106,6 +106,7 @@ const hintstrings = new Map( bigscreen: `Bigscreen! Did you know you can have the chat on the left or right side of the stream by clicking the swap icon in the top left?`, danisold: 'Destiny is an Amazon Associate. He earns a commission on qualifying purchases of any product on Amazon linked in Destiny.gg chat.', + cantremoveevent: 'This event could not be removed.', }), ); diff --git a/assets/chat/js/event-bar/EventBar.js b/assets/chat/js/event-bar/EventBar.js new file mode 100644 index 00000000..69c5bbbc --- /dev/null +++ b/assets/chat/js/event-bar/EventBar.js @@ -0,0 +1,172 @@ +import EventEmitter from '../emitter'; + +/** + * @typedef {import('../messages/ChatEventMessage').default & {expirationTimestamp: number}} ExpiringEvent + */ + +export default class ChatEventBar extends EventEmitter { + events = []; + + constructor() { + super(); + /** @type HTMLDivElement */ + this.eventBarUI = document.getElementById('chat-event-bar'); + /** @type HTMLDivElement */ + this.eventSelectUI = document.getElementById('chat-event-selected'); + + this.eventBarUI.addEventListener('wheel', (event) => { + if (event.deltaX === 0) { + event.preventDefault(); + this.eventBarUI.scrollBy({ + left: event.deltaY > 0 ? 30 : -30, + }); + } + }); + + this.eventSelectUI.addEventListener('click', (e) => { + // Don't unselect if the selected event message is clicked + if (e.target !== e.currentTarget) { + return; + } + + // Prevent the click from canceling focus, if enabled + e.stopPropagation(); + + this.unselect(); + }); + } + + /** + * Adds the event to the event bar. + * @param {EventBarEvent} event + * @param {boolean} animate Animate the addition of the event + */ + add(event, animate = true) { + if (!this.shouldEventBeDisplayed(event.data)) { + return; + } + + this.events.push(event); + + event.element.addEventListener('click', () => { + this.select(event.selectedElement); + }); + event.on('eventExpired', this.removeEvent.bind(this)); + + if (animate) { + event.element.classList.add('enter'); + } + + this.eventBarUI.prepend(event.element); + + // // Update chat window to fix the scroll position + // this.chat.mainwindow.update(); + // + event.startExpiry(); + } + + /** + * Unselects the currently highlighted event. + */ + unselect() { + if (this.isEventSelected()) { + this.eventSelectUI.replaceChildren(); + this.eventSelectUI.classList.add('hidden'); + this.emit('eventUnselected'); + } + } + + /** + * Selects the specified event. + * @param {HTMLDivElement} event + */ + select(event) { + /** @type HTMLDivElement */ + event.classList.add('event-bar-selected-message'); + + this.eventSelectUI.replaceChildren(); + this.eventSelectUI.append(event); + this.eventSelectUI.classList.remove('hidden'); + + this.emit('eventSelected'); + } + + /** + * Returns true if an event is currently selected + */ + isEventSelected() { + return this.eventSelectUI.hasChildNodes(); + } + + /** + * Checks if the specified event is already in the event bar. + * @param {string} uuid + * @returns {boolean} + */ + contains(uuid) { + return this.events.some((e) => e.uuid === uuid); + } + + /** + * Sorts the events in the event bar in descending order by time received. + */ + sort() { + [...this.eventBarUI.children] + .sort((a, b) => + Number(a.dataset.unixtimestamp) < Number(b.dataset.unixtimestamp) + ? 1 + : -1, + ) + .forEach((node) => this.eventBarUI.appendChild(node)); + } + + /** + * Checks if the specified event should appear in the event bar. + * @param {ExpiringEvent} event + * @returns {boolean} + * @private + */ + shouldEventBeDisplayed(event) { + if (this.contains(event.uuid)) { + return false; + } + + const currentTimestamp = Date.now(); + if (event.expirationTimestamp < currentTimestamp) { + return false; + } + + // subscriptions from a mass gift event don't appear in the event bar + // to avoid overcrowding it (one event showing how many gifts a person bought is enough) + if (event.fromMassGift) { + return false; + } + + return true; + } + + removeEvent(event) { + this.events = this.events.filter((e) => e.uuid !== event.uuid); + event.remove(); + } + + removeAllEvents() { + for (const e of this.events) { + e.remove(false); + } + + this.events = []; + } + + replaceEvents(events) { + this.removeAllEvents(); + + for (const e of events) { + this.add(e, false); + } + } + + get length() { + return this.eventBarUI.querySelectorAll(`.event-bar-event`).length; + } +} diff --git a/assets/chat/js/event-bar/EventBarEvent.js b/assets/chat/js/event-bar/EventBarEvent.js new file mode 100644 index 00000000..fdf14a84 --- /dev/null +++ b/assets/chat/js/event-bar/EventBarEvent.js @@ -0,0 +1,169 @@ +import { usernameColorFlair } from '../messages/ChatUserMessage'; +import { selectDonationTier } from '../messages/ChatDonationMessage'; +import { getTierStyles } from '../messages/subscriptions/ChatSubscriptionMessage'; +import { MessageBuilder, MessageTypes } from '../messages'; +import ChatUser from '../user'; +import EventEmitter from '../emitter'; + +export default class EventBarEvent extends EventEmitter { + /** + * @param {*} chat + * @param {string} type + * @param {import('./EventBar').ExpiringEvent} data + */ + constructor(chat, type, data) { + super(); + + this.type = type; + this.data = data; + + this.element = this.buildElement(chat); + } + + /** + * @param {*} chat + * @private + */ + buildElement(chat) { + /** @type HTMLDivElement */ + const eventTemplate = document + .querySelector('#event-bar-event-template') + ?.content.cloneNode(true).firstElementChild; + + eventTemplate.classList.add(this.type.toLowerCase()); + eventTemplate.dataset.uuid = this.data.uuid; + eventTemplate.dataset.unixtimestamp = this.data.timestamp; + + const user = new ChatUser(this.data.user); + const userTemplate = eventTemplate.querySelector('.user'); + const colorFlair = usernameColorFlair(chat.flairs, user); + if (colorFlair) { + userTemplate.classList.add(colorFlair.name); + } + userTemplate.textContent = user.displayName; + + const iconTemplate = eventTemplate.querySelector('.event-icon'); + iconTemplate.classList.add(this.type.toLowerCase()); + + switch (this.type) { + case MessageTypes.SUBSCRIPTION: + case MessageTypes.GIFTSUB: + case MessageTypes.MASSGIFT: { + const { rainbowColor, tierColor } = getTierStyles( + this.data.tier, + chat.flairs, + ); + + if (tierColor) eventTemplate.style.borderColor = tierColor; + if (rainbowColor) eventTemplate.classList.add('rainbow-border'); + break; + } + case MessageTypes.DONATION: { + const donationTier = selectDonationTier(this.data.amount); + eventTemplate.classList.add(donationTier[0]); + break; + } + default: + break; + } + + this.selectedElement = this.buildSelected().html(chat); + + return eventTemplate; + } + + startExpiry() { + this.expiryPercentage = this.calculateExpiryPercentage(); + + this.intervalID = setInterval(() => { + const percentageLeft = this.calculateExpiryPercentage(); + + if (percentageLeft <= 0) { + this.expire(); + return; + } + + this.expiryPercentage = percentageLeft; + }, 1000); + } + + /** + * Calculates percentage left until the event expires. + * @returns {number} + */ + calculateExpiryPercentage() { + const currentTimestamp = Date.now(); + const eventTimeLeft = this.data.expirationTimestamp - currentTimestamp; + const eventFullDuration = + this.data.expirationTimestamp - this.data.timestamp; + + return (eventTimeLeft * 100) / eventFullDuration; + } + + get expiryPercentage() { + return Number(this.element.dataset.percentageLeft); + } + + /** + * Sets the progress gradient of the event. + * @param {number} percentageLeft + */ + set expiryPercentage(percentageLeft) { + this.element.dataset.percentageLeft = percentageLeft; + this.element.querySelector('.event-contents').style.background = + `linear-gradient(90deg, #282828, #282828 ${percentageLeft}%, #151515 ${percentageLeft}%, #151515)`; + } + + buildSelected() { + switch (this.type) { + case MessageTypes.SUBSCRIPTION: { + return MessageBuilder.subscription(this.data); + } + case MessageTypes.GIFTSUB: { + return MessageBuilder.gift(this.data); + } + case MessageTypes.MASSGIFT: { + return MessageBuilder.massgift(this.data); + } + case MessageTypes.DONATION: { + return MessageBuilder.donation(this.data); + } + default: + return undefined; + } + } + + /** + * @private + */ + expire() { + this.stopUpdatingExpirationProgressBar(); + this.emit('eventExpired', this); + } + + /** + * @param {boolean} animate Animate the removal of the event + */ + remove(animate = true) { + this.stopUpdatingExpirationProgressBar(); + + if (animate) { + this.element.addEventListener('animationend', () => { + this.element.remove(); + }); + this.element.classList.add('removed'); + } else { + this.element.remove(); + } + } + + stopUpdatingExpirationProgressBar() { + if (this.intervalID) { + clearInterval(this.intervalID); + } + } + + get uuid() { + return this.data.uuid; + } +} diff --git a/assets/chat/js/focus.js b/assets/chat/js/focus.js index f00997c9..9330c16a 100644 --- a/assets/chat/js/focus.js +++ b/assets/chat/js/focus.js @@ -9,7 +9,9 @@ class ChatUserFocus { this.chat = chat; this.css = css; this.focused = []; - this.chat.output.on('click', (e) => this.toggleElement(e.target)); + this.chat.output.on('click', (e) => { + this.toggleElement(e.target); + }); } toggleElement(target) { @@ -22,7 +24,7 @@ class ChatUserFocus { this.toggleFocus(t.text()); } else if (t.hasClass('flair')) { this.toggleFocus(t.data('flair'), true); - } else if (this.focused.length > 0) { + } else if (this.isFocused()) { this.clearFocus(); } } @@ -41,6 +43,10 @@ class ChatUserFocus { return this; } + isFocused() { + return this.focused.length > 0; + } + addCssRule(value, isFlair) { let rule; if (isFlair) { @@ -90,7 +96,7 @@ class ChatUserFocus { } redraw() { - this.chat.ui.toggleClass('focus', this.focused.length > 0); + this.chat.ui.toggleClass('focus', this.isFocused()); } } diff --git a/assets/chat/js/menus/ChatEmoteMenu.js b/assets/chat/js/menus/ChatEmoteMenu.js index dba61a99..7f01df9d 100644 --- a/assets/chat/js/menus/ChatEmoteMenu.js +++ b/assets/chat/js/menus/ChatEmoteMenu.js @@ -6,7 +6,6 @@ export default class ChatEmoteMenu extends ChatMenu { super(ui, btn, chat); this.searchterm = ''; this.emoteMenuContent = this.ui.find('.all .content'); - this.favoriteEmoteMenuContent = this.ui.find('.favorite .content'); this.searchinput = this.ui.find( '#chat-emote-list-search .form-control:first', ); @@ -23,7 +22,6 @@ export default class ChatEmoteMenu extends ChatMenu { () => { this.searchterm = this.searchinput.val(); this.buildEmoteMenu(); - this.buildFavoriteEmoteMenu(); }, { atBegin: false }, ), @@ -34,30 +32,13 @@ export default class ChatEmoteMenu extends ChatMenu { super.show(); this.searchinput.focus(); this.buildEmoteMenu(); - this.buildFavoriteEmoteMenu(); } - buildFavoriteEmoteMenu() { + buildEmoteMenu() { const favoriteEmotes = [...this.chat.favoriteemotes].filter((e) => this.chat.emoteService.hasEmote(e), ); - if (favoriteEmotes.length === 0) { - this.favoriteEmoteMenuContent.html(`
-
Favorite Emotes
-

Right click an emote and favorite it!

-
`); - return; - } - const emotesStr = favoriteEmotes - .map((e) => this.buildEmoteItem(e, false)) - .join(''); - this.favoriteEmoteMenuContent.html(`
-
Favorite Emotes
-
${emotesStr}
-
`); - } - buildEmoteMenu() { this.emoteMenuContent.empty(); this.chat.emoteService.tiers.forEach((tier) => { @@ -68,7 +49,7 @@ export default class ChatEmoteMenu extends ChatMenu { const locked = tier > this.chat.user.subTier && !this.chat.user.isPrivileged(); this.emoteMenuContent.append( - this.buildEmoteMenuSection(title, emotes, locked), + this.buildEmoteMenuSection(title, emotes, favoriteEmotes, locked), ); }); @@ -80,9 +61,19 @@ export default class ChatEmoteMenu extends ChatMenu { } } - buildEmoteMenuSection(title, emotes, disabled = false) { - const emotesStr = emotes - .map((e) => this.buildEmoteItem(e, disabled)) + buildEmoteMenuSection(title, emotes, favoriteEmotes, disabled = false) { + let emotesStr = ''; + if (favoriteEmotes.length > 0) { + emotesStr += favoriteEmotes + .map((e) => this.buildEmoteItem(e, true, disabled)) + .join(''); + } + emotesStr += emotes + .map((e) => + !favoriteEmotes.includes(e) + ? this.buildEmoteItem(e, false, disabled) + : null, + ) .join(''); if (emotesStr !== '') { return `
@@ -97,16 +88,16 @@ export default class ChatEmoteMenu extends ChatMenu { return ''; } - buildEmoteItem(emote, disabled) { + buildEmoteItem(emote, favorite, disabled) { if (this.searchterm && this.searchterm.length > 0) { if (emote.toLowerCase().indexOf(this.searchterm.toLowerCase()) >= 0) { - return `
${emote}
`; } return ''; } - return `
${emote}
`; } diff --git a/assets/chat/js/menus/ChatEmoteTooltip.js b/assets/chat/js/menus/ChatEmoteTooltip.js index 31ffc766..2e0436a3 100644 --- a/assets/chat/js/menus/ChatEmoteTooltip.js +++ b/assets/chat/js/menus/ChatEmoteTooltip.js @@ -32,6 +32,7 @@ export default class ChatEmoteTooltip extends ChatMenuFloating { this.ui.favorite.on('click', () => { const result = this.chat.toggleFavoriteEmote(this.emote); + this.hide(); this.favorite = result; }); } diff --git a/assets/chat/js/menus/ChatEventActionMenu.js b/assets/chat/js/menus/ChatEventActionMenu.js new file mode 100644 index 00000000..7c1c6c14 --- /dev/null +++ b/assets/chat/js/menus/ChatEventActionMenu.js @@ -0,0 +1,24 @@ +import ChatMenuFloating from './ChatMenuFloating'; + +export default class ChatEventActionMenu extends ChatMenuFloating { + constructor(ui, btn, chat) { + super(ui, btn, chat); + + this.chat.ui.on('click', '.msg-event .event-button', (e) => { + this.openMenu(e); + return false; + }); + + this.ui.on('click', '#remove-event-button', this.removeEvent.bind(this)); + } + + openMenu(e) { + this.eventElement = e.currentTarget.closest('.msg-event'); + this.position(e); + this.show(); + } + + removeEvent() { + this.emit('removeEvent', this.eventElement.dataset.uuid); + } +} diff --git a/assets/chat/js/menus/index.js b/assets/chat/js/menus/index.js index 27fe072a..90ccead1 100644 --- a/assets/chat/js/menus/index.js +++ b/assets/chat/js/menus/index.js @@ -5,3 +5,4 @@ export { default as ChatEmoteMenu } from './ChatEmoteMenu'; export { default as ChatEmoteTooltip } from './ChatEmoteTooltip'; export { default as ChatWhisperUsers } from './ChatWhisperUsers'; export { default as ChatUserInfoMenu } from './ChatUserInfoMenu'; +export { default as ChatEventActionMenu } from './ChatEventActionMenu'; diff --git a/assets/chat/js/messages/ChatBroadcastMessage.js b/assets/chat/js/messages/ChatBroadcastMessage.js index 67a24d9e..a1c1ced8 100644 --- a/assets/chat/js/messages/ChatBroadcastMessage.js +++ b/assets/chat/js/messages/ChatBroadcastMessage.js @@ -61,4 +61,8 @@ export default class ChatBroadcastMessage extends ChatEventMessage { return this.wrap(eventTemplate.innerHTML, classes, attributes); } + + get hasActions() { + return false; + } } diff --git a/assets/chat/js/messages/ChatDonationMessage.js b/assets/chat/js/messages/ChatDonationMessage.js index f3de6e76..45148b0e 100644 --- a/assets/chat/js/messages/ChatDonationMessage.js +++ b/assets/chat/js/messages/ChatDonationMessage.js @@ -4,22 +4,23 @@ import MessageTypes from './MessageTypes'; const DONATION_TIERS = [0, 5, 10, 25, 50, 100]; +/** + * Toggles the correct classes for a specific donation amount. + * @param {number} amount + * @returns {array} + */ +export function selectDonationTier(amount) { + const tier = DONATION_TIERS.findIndex((value) => amount < value * 100); + return [`amount-${tier !== -1 ? DONATION_TIERS[tier - 1] : '100'}`]; +} + export default class ChatDonationMessage extends ChatEventMessage { - constructor(message, user, amount, timestamp) { - super(message, timestamp); + constructor(message, user, amount, timestamp, expirationTimestamp, uuid) { + super(message, timestamp, uuid); this.user = user; this.type = MessageTypes.DONATION; this.amount = amount; - } - - /** - * Toggles the correct classes for a specific donation amount. - * @param {number} amount - * @returns {array} - */ - selectDonationTier(amount) { - const tier = DONATION_TIERS.findIndex((value) => amount < value * 100); - return [`amount-${tier !== -1 ? DONATION_TIERS[tier - 1] : '100'}`]; + this.expirationTimestamp = expirationTimestamp; } html(chat = null) { @@ -44,7 +45,7 @@ export default class ChatDonationMessage extends ChatEventMessage { })}`, ); - const donationTier = this.selectDonationTier(this.amount); + const donationTier = selectDonationTier(this.amount); eventTemplate.classList.add(donationTier[0]); eventTemplate .querySelector('.event-icon') diff --git a/assets/chat/js/messages/ChatEventMessage.js b/assets/chat/js/messages/ChatEventMessage.js index 06834cad..7e666a9c 100644 --- a/assets/chat/js/messages/ChatEventMessage.js +++ b/assets/chat/js/messages/ChatEventMessage.js @@ -1,13 +1,14 @@ import ChatMessage from './ChatMessage'; export default class ChatEventMessage extends ChatMessage { - constructor(message, timestamp) { + constructor(message, timestamp, uuid) { super(message, timestamp); this.tag = null; this.title = ''; this.slashme = false; this.isown = false; this.mentioned = []; + this.uuid = uuid; } html(chat = null) { @@ -29,10 +30,21 @@ export default class ChatEventMessage extends ChatMessage { eventTemplate.querySelector('.event-bottom').remove(); } + if (!this.hasActions || !chat.user?.hasModPowers()) { + const eventButton = eventTemplate.querySelector('.event-button'); + eventButton.disabled = true; + } + + eventTemplate.dataset.uuid = this.uuid; + return eventTemplate; } updateTimeFormat() { // This avoids errors. Timestamps aren't rendered in event messages. } + + get hasActions() { + return true; + } } diff --git a/assets/chat/js/messages/MessageBuilder.js b/assets/chat/js/messages/MessageBuilder.js index 53739c20..5349d760 100644 --- a/assets/chat/js/messages/MessageBuilder.js +++ b/assets/chat/js/messages/MessageBuilder.js @@ -68,8 +68,10 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, data.streak, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -80,8 +82,11 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, new ChatUser(data.recipient), + data.fromMassGift, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -92,8 +97,10 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, data.quantity, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -104,6 +111,7 @@ export default class MessageBuilder { new ChatUser(data.user), data.amount, data.timestamp, + data.expirationTimestamp, data.uuid, ); } diff --git a/assets/chat/js/messages/PinnedMessage.js b/assets/chat/js/messages/PinnedMessage.js index cd83c2a1..707dcd31 100644 --- a/assets/chat/js/messages/PinnedMessage.js +++ b/assets/chat/js/messages/PinnedMessage.js @@ -45,15 +45,23 @@ export default class PinnedMessage extends ChatUserMessage { /** * Shows/hides the current message. * @param {boolean} state - * @returns {null} null */ - set visible(state) { + set displayed(state) { this.ui.classList.toggle('hidden', !state); document .getElementById('chat-pinned-show-btn') ?.classList.toggle('active', !state); } + /** + * Shows/hides the full pinned message frame. + * @param {boolean} state + */ + set hidden(state) { + const frame = document.getElementById('chat-pinned-message'); + frame.classList.toggle('active', !state); + } + /** * Unpins the current message. * @returns {null} null @@ -61,8 +69,8 @@ export default class PinnedMessage extends ChatUserMessage { unpin() { dismissPin(this.uuid); - const frame = document.getElementById('chat-pinned-frame'); - frame.classList.toggle('active', false); + this.hidden = true; + const frame = document.getElementById('chat-pinned-message'); frame.replaceChildren(); return null; @@ -77,7 +85,7 @@ export default class PinnedMessage extends ChatUserMessage { pin(chat = null, visibility = true) { this.ui.id = 'msg-pinned'; this.ui.classList.toggle('msg-pinned', true); - this.visible = visibility; + this.displayed = visibility; this.ui.querySelector('span.features').classList.toggle('hidden', true); chat.mainwindow.update(); @@ -108,7 +116,7 @@ export default class PinnedMessage extends ChatUserMessage { showPin.title = 'Show Pinned Message'; showPin.addEventListener('click', () => { - this.visible = true; + this.displayed = true; }); const closePin = document.createElement('a'); @@ -122,12 +130,12 @@ export default class PinnedMessage extends ChatUserMessage { closePin.addEventListener('click', () => { dismissPin(this.uuid); - this.visible = false; + this.displayed = false; }); this.ui.prepend(closePin); - const pinnedFrame = document.getElementById('chat-pinned-frame'); + const pinnedFrame = document.getElementById('chat-pinned-message'); pinnedFrame.classList.toggle('active', true); pinnedFrame.prepend(this.ui); pinnedFrame.prepend(showPin); diff --git a/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js index b0e39ea6..96a0166c 100644 --- a/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js @@ -3,10 +3,22 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatGiftedSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, giftee, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + giftee, + fromMassGift, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.GIFTSUB; this.giftee = giftee; + this.fromMassGift = fromMassGift; } html(chat = null) { diff --git a/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js index a2e9ed6b..9bb82712 100644 --- a/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js @@ -2,8 +2,18 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatMassSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, quantity, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + quantity, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.MASSGIFT; this.quantity = quantity; } diff --git a/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js index 797a4f98..ba29a5db 100644 --- a/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js @@ -2,8 +2,18 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatRegularSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, streak, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + streak, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.SUBSCRIPTION; this.streak = streak; } diff --git a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js index 7a076d3b..71bcf089 100644 --- a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js @@ -2,32 +2,46 @@ import { usernameColorFlair } from '../ChatUserMessage'; import ChatEventMessage from '../ChatEventMessage'; import features from '../../features'; +export function getTierStyles(tier, flairs) { + const tierFlair = features[`SUB_TIER_${tier}`]; + const tierInfo = flairs.find((el) => el.name === tierFlair); + const tierColor = tierInfo?.color; + + const tierClass = tierInfo?.rainbowColor ? `user ${tierFlair}` : ''; + + return { + rainbowColor: tierInfo?.rainbowColor, + tierClass, + tierColor: tierInfo?.rainbowColor ? '' : tierColor, + }; +} + export default class ChatSubscriptionMessage extends ChatEventMessage { - constructor(message, user, tier, tierLabel, timestamp) { - super(message, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + timestamp, + expirationTimestamp, + uuid, + ) { + super(message, timestamp, uuid); this.user = user; this.tier = tier; this.tierLabel = tierLabel; - } - - getTierStyles(chat = null) { - const tierFlair = features[`SUB_TIER_${this.tier}`]; - const tierInfo = chat.flairs.find((el) => el.name === tierFlair); - const tierColor = tierInfo?.color; - - const tierClass = tierInfo?.rainbowColor ? `user ${tierFlair}` : ''; - - return { - rainbowColor: tierInfo?.rainbowColor, - tierClass, - tierColor: tierInfo?.rainbowColor ? '' : tierColor, - }; + this.amount = amount; + this.expirationTimestamp = expirationTimestamp; } html(chat = null) { const eventTemplate = super.html(chat); - const { rainbowColor, tierClass, tierColor } = this.getTierStyles(chat); + const { rainbowColor, tierClass, tierColor } = getTierStyles( + this.tier, + chat.flairs, + ); if (tierColor) eventTemplate.style.borderColor = tierColor; if (rainbowColor) eventTemplate.classList.add('rainbow-border'); diff --git a/assets/chat/js/window.js b/assets/chat/js/window.js index a491bc41..0404a4cb 100644 --- a/assets/chat/js/window.js +++ b/assets/chat/js/window.js @@ -137,6 +137,14 @@ class ChatWindow extends EventEmitter { } } + isScrollPinned() { + return this.scrollplugin.pinned; + } + + scrollBottom() { + this.scrollplugin.scrollBottom(); + } + /** * Use chat state (settings and authentication data) to update the messages in * this window. @@ -148,12 +156,19 @@ class ChatWindow extends EventEmitter { } if (message.user?.isSystem()) { - return; + continue; } const username = message.user?.username; - if (message.type !== MessageTypes.UI) { + if ( + ![ + MessageTypes.UI, + MessageTypes.INFO, + MessageTypes.ERROR, + MessageTypes.STATUS, + ].includes(message.type) + ) { message.ignore(chat.ignored(username, message.message)); } diff --git a/assets/views/embed.html b/assets/views/embed.html index 2d604df9..a1e854db 100644 --- a/assets/views/embed.html +++ b/assets/views/embed.html @@ -1,6 +1,8 @@
+
+
@@ -24,7 +26,8 @@
-
+ +
@@ -322,9 +325,6 @@
Emotes
-
-
-
+ +
+
+ +
+
diff --git a/assets/views/stream.html b/assets/views/stream.html index 0adad494..37e9b2b6 100644 --- a/assets/views/stream.html +++ b/assets/views/stream.html @@ -1,6 +1,8 @@
+
-
+
+
diff --git a/assets/views/templates.html b/assets/views/templates.html index 22d7d42d..fa5cae10 100644 --- a/assets/views/templates.html +++ b/assets/views/templates.html @@ -1,9 +1,11 @@