Add support for changing user color in name modal. Closes #1805

This commit is contained in:
Gabe Kangas 2022-08-09 19:56:45 -07:00
parent 9187a7a435
commit 68414445c2
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
22 changed files with 171 additions and 55 deletions

View File

@ -11,6 +11,9 @@ const (
DataDirectory = "data" DataDirectory = "data"
// EmojiDir is relative to the static directory. // EmojiDir is relative to the static directory.
EmojiDir = "/img/emoji" EmojiDir = "/img/emoji"
// MaxUserColor is the largest color value available to assign to users.
// They start at 0 and can be treated as IDs more than colors themselves.
MaxUserColor = 7
) )
var ( var (

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/controllers" "github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core/user" "github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/utils" "github.com/owncast/owncast/utils"
@ -41,7 +42,7 @@ func CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) {
return return
} }
color := utils.GenerateRandomDisplayColor() color := utils.GenerateRandomDisplayColor(config.MaxUserColor)
if err := user.InsertExternalAPIUser(token, request.Name, color, request.Scopes); err != nil { if err := user.InsertExternalAPIUser(token, request.Name, color, request.Scopes); err != nil {
controllers.InternalErrorHandler(w, err) controllers.InternalErrorHandler(w, err)

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/user" "github.com/owncast/owncast/core/user"
@ -91,6 +92,25 @@ func (s *Server) userNameChanged(eventData chatClientEvent) {
eventData.client.sendConnectedClientInfo() eventData.client.sendConnectedClientInfo()
} }
func (s *Server) userColorChanged(eventData chatClientEvent) {
var receivedEvent events.ColorChangeEvent
if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil {
log.Errorln("error unmarshalling to ColorChangeEvent", err)
return
}
// Verify this color is valid
if receivedEvent.NewColor > config.MaxUserColor {
log.Errorln("invalid color requested when changing user display color")
return
}
// Save the new color
if err := user.ChangeUserColor(eventData.client.User.ID, receivedEvent.NewColor); err != nil {
log.Errorln("error changing user display color", err)
}
}
func (s *Server) userMessageSent(eventData chatClientEvent) { func (s *Server) userMessageSent(eventData chatClientEvent) {
var event events.UserMessageEvent var event events.UserMessageEvent
if err := json.Unmarshal(eventData.data, &event); err != nil { if err := json.Unmarshal(eventData.data, &event); err != nil {

View File

@ -10,6 +10,8 @@ const (
UserJoined EventType = "USER_JOINED" UserJoined EventType = "USER_JOINED"
// UserNameChanged is the event sent when a chat username change takes place. // UserNameChanged is the event sent when a chat username change takes place.
UserNameChanged EventType = "NAME_CHANGE" UserNameChanged EventType = "NAME_CHANGE"
// UserColorChanged is the event sent when a chat user color change takes place.
UserColorChanged EventType = "COLOR_CHANGE"
// VisibiltyUpdate is the event sent when a chat message's visibility changes. // VisibiltyUpdate is the event sent when a chat message's visibility changes.
VisibiltyUpdate EventType = "VISIBILITY-UPDATE" VisibiltyUpdate EventType = "VISIBILITY-UPDATE"
// PING is a ping message. // PING is a ping message.

View File

@ -7,6 +7,13 @@ type NameChangeEvent struct {
NewName string `json:"newName"` NewName string `json:"newName"`
} }
// ColorChangeEvent is received when a user changes their chat display color.
type ColorChangeEvent struct {
Event
UserEvent
NewColor int `json:"newColor"`
}
// NameChangeBroadcast represents a user changing their chat display name. // NameChangeBroadcast represents a user changing their chat display name.
type NameChangeBroadcast struct { type NameChangeBroadcast struct {
Event Event

View File

@ -359,6 +359,8 @@ func (s *Server) eventReceived(event chatClientEvent) {
case events.UserNameChanged: case events.UserNameChanged:
s.userNameChanged(event) s.userNameChanged(event)
case events.UserColorChanged:
s.userColorChanged(event)
default: default:
log.Debugln(eventType, "event not found:", typecheck) log.Debugln(eventType, "event not found:", typecheck)
} }

View File

@ -291,7 +291,7 @@ func migrateToSchema1(db *sql.DB) {
// Recreate them as users // Recreate them as users
for _, token := range oldAccessTokens { for _, token := range oldAccessTokens {
color := utils.GenerateRandomDisplayColor() color := utils.GenerateRandomDisplayColor(config.MaxUserColor)
if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil { if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil {
log.Errorln("Error migrating access token", err) log.Errorln("Error migrating access token", err)
} }

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/db" "github.com/owncast/owncast/db"
"github.com/owncast/owncast/utils" "github.com/owncast/owncast/utils"
@ -70,7 +71,7 @@ func CreateAnonymousUser(displayName string) (*User, string, error) {
} }
} }
displayColor := utils.GenerateRandomDisplayColor() displayColor := utils.GenerateRandomDisplayColor(config.MaxUserColor)
user := &User{ user := &User{
ID: id, ID: id,
@ -125,6 +126,21 @@ func ChangeUsername(userID string, username string) error {
return nil return nil
} }
// ChangeUserColor will change the user associated to userID from one display name to another.
func ChangeUserColor(userID string, color int) error {
_datastore.DbLock.Lock()
defer _datastore.DbLock.Unlock()
if err := _datastore.GetQueries().ChangeDisplayColor(context.Background(), db.ChangeDisplayColorParams{
DisplayColor: int32(color),
ID: userID,
}); err != nil {
return errors.Wrap(err, "unable to change display color")
}
return nil
}
func addAccessTokenForUser(accessToken, userID string) error { func addAccessTokenForUser(accessToken, userID string) error {
return _datastore.GetQueries().AddAccessTokenForUser(context.Background(), db.AddAccessTokenForUserParams{ return _datastore.GetQueries().AddAccessTokenForUser(context.Background(), db.AddAccessTokenForUserParams{
Token: accessToken, Token: accessToken,

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.14.0 // sqlc v1.15.0
package db package db

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.14.0 // sqlc v1.15.0
package db package db

View File

@ -105,3 +105,6 @@ SELECT count(*) FROM users WHERE display_name = $1 AND authenticated_at is not n
-- name: ChangeDisplayName :exec -- name: ChangeDisplayName :exec
UPDATE users SET display_name = $1, previous_names = previous_names || $2, namechanged_at = $3 WHERE id = $4; UPDATE users SET display_name = $1, previous_names = previous_names || $2, namechanged_at = $3 WHERE id = $4;
-- name: ChangeDisplayColor :exec
UPDATE users SET display_color = $1 WHERE id = $2;

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.14.0 // sqlc v1.15.0
// source: query.sql // source: query.sql
package db package db
@ -153,6 +153,20 @@ func (q *Queries) BanIPAddress(ctx context.Context, arg BanIPAddressParams) erro
return err return err
} }
const changeDisplayColor = `-- name: ChangeDisplayColor :exec
UPDATE users SET display_color = $1 WHERE id = $2
`
type ChangeDisplayColorParams struct {
DisplayColor int32
ID string
}
func (q *Queries) ChangeDisplayColor(ctx context.Context, arg ChangeDisplayColorParams) error {
_, err := q.db.ExecContext(ctx, changeDisplayColor, arg.DisplayColor, arg.ID)
return err
}
const changeDisplayName = `-- name: ChangeDisplayName :exec const changeDisplayName = `-- name: ChangeDisplayName :exec
UPDATE users SET display_name = $1, previous_names = previous_names || $2, namechanged_at = $3 WHERE id = $4 UPDATE users SET display_name = $1, previous_names = previous_names || $2, namechanged_at = $3 WHERE id = $4
` `

View File

@ -324,10 +324,10 @@ func StringMapKeys(stringMap map[string]interface{}) []string {
// GenerateRandomDisplayColor will return a random number that is used for // GenerateRandomDisplayColor will return a random number that is used for
// referencing a color value client-side. These colors are seen as // referencing a color value client-side. These colors are seen as
// --theme-user-colors-n. // --theme-user-colors-n.
func GenerateRandomDisplayColor() int { func GenerateRandomDisplayColor(maxColor int) int {
rangeLower := 1 rangeLower := 0
rangeUpper := 8 rangeUpper := maxColor
return rangeLower + rand.Intn(rangeUpper-rangeLower+1) //nolint return rangeLower + rand.Intn(rangeUpper-rangeLower+1) //nolint:gosec
} }
// GetHostnameFromURL will return the hostname component from a URL string. // GetHostnameFromURL will return the hostname component from a URL string.

View File

@ -1,16 +1,34 @@
import { useState } from 'react'; import React, { CSSProperties, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Input, Button } from 'antd'; import { Input, Button, Select } from 'antd';
import { MessageType } from '../../interfaces/socket-events'; import { MessageType } from '../../interfaces/socket-events';
import WebsocketService from '../../services/websocket-service'; import WebsocketService from '../../services/websocket-service';
import { websocketServiceAtom, chatDisplayNameAtom } from '../stores/ClientConfigStore'; import {
websocketServiceAtom,
chatDisplayNameAtom,
chatDisplayColorAtom,
} from '../stores/ClientConfigStore';
const { Option } = Select;
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {} interface Props {}
function UserColor(props: { color: number }): React.ReactElement {
const { color } = props;
const style: CSSProperties = {
textAlign: 'center',
backgroundColor: `var(--theme-user-colors-${color})`,
width: '100%',
height: '100%',
};
return <div style={style} />;
}
export default function NameChangeModal(props: Props) { export default function NameChangeModal(props: Props) {
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom); const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom); const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatDisplayColor = useRecoilValue<number>(chatDisplayColorAtom);
const [newName, setNewName] = useState<any>(chatDisplayName); const [newName, setNewName] = useState<any>(chatDisplayName);
const handleNameChange = () => { const handleNameChange = () => {
@ -24,6 +42,17 @@ export default function NameChangeModal(props: Props) {
const saveEnabled = const saveEnabled =
newName !== chatDisplayName && newName !== '' && websocketService?.isConnected(); newName !== chatDisplayName && newName !== '' && websocketService?.isConnected();
const handleColorChange = (color: string) => {
const colorChange = {
type: MessageType.COLOR_CHANGE,
newColor: Number(color),
};
websocketService.send(colorChange);
};
const maxColor = 8; // 0...n
const colorOptions = [...Array(maxColor)].map((e, i) => i);
return ( return (
<div> <div>
Your chat display name is what people see when you send chat messages. Other information can Your chat display name is what people see when you send chat messages. Other information can
@ -32,13 +61,28 @@ export default function NameChangeModal(props: Props) {
value={newName} value={newName}
onChange={e => setNewName(e.target.value)} onChange={e => setNewName(e.target.value)}
placeholder="Your chat display name" placeholder="Your chat display name"
maxLength={10} maxLength={30}
showCount showCount
defaultValue={chatDisplayName} defaultValue={chatDisplayName}
/> />
<Button disabled={!saveEnabled} onClick={handleNameChange}> <Button disabled={!saveEnabled} onClick={handleNameChange}>
Change name Change name
</Button> </Button>
<div>
Your Color
<Select
style={{ width: 120 }}
onChange={handleColorChange}
defaultValue={chatDisplayColor.toString()}
getPopupContainer={triggerNode => triggerNode.parentElement}
>
{colorOptions.map(e => (
<Option key={e.toString()} title={e}>
<UserColor color={e} />
</Option>
))}
</Select>
</div>
</div> </div>
); );
} }

View File

@ -46,6 +46,11 @@ export const chatDisplayNameAtom = atom<string>({
default: null, default: null,
}); });
export const chatDisplayColorAtom = atom<number>({
key: 'chatDisplayColor',
default: null,
});
export const chatUserIdAtom = atom<string>({ export const chatUserIdAtom = atom<string>({
key: 'chatUserIdAtom', key: 'chatUserIdAtom',
default: null, default: null,
@ -149,6 +154,7 @@ export function ClientConfigStore() {
const [appState, appStateSend, appStateService] = useMachine(appStateModel); const [appState, appStateSend, appStateService] = useMachine(appStateModel);
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom); const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
const setChatDisplayColor = useSetRecoilState<Number>(chatDisplayColorAtom);
const setChatUserId = useSetRecoilState<string>(chatUserIdAtom); const setChatUserId = useSetRecoilState<string>(chatUserIdAtom);
const setIsChatModerator = useSetRecoilState<boolean>(isChatModeratorAtom); const setIsChatModerator = useSetRecoilState<boolean>(isChatModeratorAtom);
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom); const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
@ -225,7 +231,7 @@ export function ClientConfigStore() {
sendEvent(AppStateEvent.NeedsRegister); sendEvent(AppStateEvent.NeedsRegister);
const response = await ChatService.registerUser(optionalDisplayName); const response = await ChatService.registerUser(optionalDisplayName);
console.log(`ChatService -> registerUser() response: \n${response}`); console.log(`ChatService -> registerUser() response: \n${response}`);
const { accessToken: newAccessToken, displayName: newDisplayName } = response; const { accessToken: newAccessToken, displayName: newDisplayName, displayColor } = response;
if (!newAccessToken) { if (!newAccessToken) {
return; return;
} }
@ -234,6 +240,7 @@ export function ClientConfigStore() {
setAccessToken(newAccessToken); setAccessToken(newAccessToken);
setLocalStorage(ACCESS_TOKEN_KEY, newAccessToken); setLocalStorage(ACCESS_TOKEN_KEY, newAccessToken);
setChatDisplayName(newDisplayName); setChatDisplayName(newDisplayName);
setChatDisplayColor(displayColor);
} catch (e) { } catch (e) {
sendEvent(AppStateEvent.Fail); sendEvent(AppStateEvent.Fail);
console.error(`ChatService -> registerUser() ERROR: \n${e}`); console.error(`ChatService -> registerUser() ERROR: \n${e}`);
@ -255,6 +262,7 @@ export function ClientConfigStore() {
handleConnectedClientInfoMessage( handleConnectedClientInfoMessage(
message as ConnectedClientInfoEvent, message as ConnectedClientInfoEvent,
setChatDisplayName, setChatDisplayName,
setChatDisplayColor,
setChatUserId, setChatUserId,
setIsChatModerator, setIsChatModerator,
); );

View File

@ -3,12 +3,14 @@ import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events';
export default function handleConnectedClientInfoMessage( export default function handleConnectedClientInfoMessage(
message: ConnectedClientInfoEvent, message: ConnectedClientInfoEvent,
setChatDisplayName: (string) => void, setChatDisplayName: (string) => void,
setChatDisplayColor: (number) => void,
setChatUserId: (number) => void, setChatUserId: (number) => void,
setIsChatModerator: (boolean) => void, setIsChatModerator: (boolean) => void,
) { ) {
const { user } = message; const { user } = message;
const { id, displayName, scopes } = user; const { id, displayName, displayColor, scopes } = user;
setChatDisplayName(displayName); setChatDisplayName(displayName);
setChatDisplayColor(displayColor);
setChatUserId(id); setChatUserId(id);
setIsChatModerator(scopes?.includes('moderator')); setIsChatModerator(scopes?.includes('moderator'));
} }

View File

@ -4,6 +4,7 @@ export enum MessageType {
CHAT = 'CHAT', CHAT = 'CHAT',
PING = 'PING', PING = 'PING',
NAME_CHANGE = 'NAME_CHANGE', NAME_CHANGE = 'NAME_CHANGE',
COLOR_CHANGE = 'COLOR_CHANGE',
PONG = 'PONG', PONG = 'PONG',
SYSTEM = 'SYSTEM', SYSTEM = 'SYSTEM',
USER_JOINED = 'USER_JOINED', USER_JOINED = 'USER_JOINED',

View File

@ -8,6 +8,7 @@ interface UserRegistrationResponse {
id: string; id: string;
accessToken: string; accessToken: string;
displayName: string; displayName: string;
displayColor: number;
} }
class ChatService { class ChatService {

View File

@ -22,6 +22,7 @@ Toggle dark mode on and off in the above toolbar to see how these colors look on
<ColorRow <ColorRow
colors={[ colors={[
'theme-user-colors-0',
'theme-user-colors-1', 'theme-user-colors-1',
'theme-user-colors-2', 'theme-user-colors-2',
'theme-user-colors-3', 'theme-user-colors-3',
@ -29,6 +30,5 @@ Toggle dark mode on and off in the above toolbar to see how these colors look on
'theme-user-colors-5', 'theme-user-colors-5',
'theme-user-colors-6', 'theme-user-colors-6',
'theme-user-colors-7', 'theme-user-colors-7',
'theme-user-colors-8',
]} ]}
/> />

View File

@ -36,19 +36,19 @@ theme:
user-colors: user-colors:
comment: 'Colors used to display chat users.' comment: 'Colors used to display chat users.'
1: 0:
value: '{color.owncast.user.1.value}' value: '{color.owncast.user.1.value}'
2: 1:
value: '{color.owncast.user.2.value}' value: '{color.owncast.user.2.value}'
3: 2:
value: '{color.owncast.user.3.value}' value: '{color.owncast.user.3.value}'
4: 3:
value: '{color.owncast.user.4.value}' value: '{color.owncast.user.4.value}'
5: 4:
value: '{color.owncast.user.5.value}' value: '{color.owncast.user.5.value}'
6: 5:
value: '{color.owncast.user.6.value}' value: '{color.owncast.user.6.value}'
7: 6:
value: '{color.owncast.user.7.value}' value: '{color.owncast.user.7.value}'
8: 7:
value: '{color.owncast.user.8.value}' value: '{color.owncast.user.8.value}'

View File

@ -1,6 +1,6 @@
// Do not edit directly // Do not edit directly
// Generated on Tue, 12 Jul 2022 21:03:36 GMT // Generated on Wed, 10 Aug 2022 02:31:18 GMT
// //
// How to edit these values: // How to edit these values:
// Edit the corresponding token file under the style-definitions directory // Edit the corresponding token file under the style-definitions directory
@ -26,14 +26,14 @@
@theme-background-primary: #fffcf2; // The main background color of the page. @theme-background-primary: #fffcf2; // The main background color of the page.
@theme-background-secondary: #f0efe4; // A secondary background color used in sections and controls. @theme-background-secondary: #f0efe4; // A secondary background color used in sections and controls.
@theme-rounded-corners: 0.5em; @theme-rounded-corners: 0.5em;
@theme-user-colors-1: #f40b0b; @theme-user-colors-0: #f40b0b;
@theme-user-colors-2: #f4800b; @theme-user-colors-1: #f4800b;
@theme-user-colors-3: #f4f40b; @theme-user-colors-2: #f4f40b;
@theme-user-colors-4: #58f40b; @theme-user-colors-3: #58f40b;
@theme-user-colors-5: #0bf4f4; @theme-user-colors-4: #0bf4f4;
@theme-user-colors-6: #0ba6f4; @theme-user-colors-5: #0ba6f4;
@theme-user-colors-7: #6666ff; @theme-user-colors-6: #6666ff;
@theme-user-colors-8: #f40bf4; @theme-user-colors-7: #f40bf4;
@owncast-purple: #00ff00; @owncast-purple: #00ff00;
@owncast-purple-25: rgba(120, 113, 255, 0.25); @owncast-purple-25: rgba(120, 113, 255, 0.25);
@owncast-purple-50: rgba(120, 113, 255, 0.5); @owncast-purple-50: rgba(120, 113, 255, 0.5);

View File

@ -1,6 +1,6 @@
/** /**
* Do not edit directly * Do not edit directly
* Generated on Tue, 12 Jul 2022 21:03:36 GMT * Generated on Wed, 10 Aug 2022 02:31:18 GMT
* *
* How to edit these values: * How to edit these values:
* Edit the corresponding token file under the style-definitions directory * Edit the corresponding token file under the style-definitions directory
@ -23,23 +23,19 @@
--theme-text-primary: #030208; /* The color of the text in the application. */ --theme-text-primary: #030208; /* The color of the text in the application. */
--theme-text-secondary: #63638e; --theme-text-secondary: #63638e;
--theme-text-link: #5353a6; --theme-text-link: #5353a6;
--theme-text-body-font-family: 'Open Sans', system-ui, -apple-system, BlinkMacSystemFont, --theme-text-body-font-family: 'Open Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', --theme-text-display-font-family: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--theme-text-display-font-family: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--theme-background-primary: #fffcf2; /* The main background color of the page. */ --theme-background-primary: #fffcf2; /* The main background color of the page. */
--theme-background-secondary: #f0efe4; /* A secondary background color used in sections and controls. */ --theme-background-secondary: #f0efe4; /* A secondary background color used in sections and controls. */
--theme-rounded-corners: 0.5em; --theme-rounded-corners: 0.5em;
--theme-user-colors-1: #f40b0b; --theme-user-colors-0: #f40b0b;
--theme-user-colors-2: #f4800b; --theme-user-colors-1: #f4800b;
--theme-user-colors-3: #f4f40b; --theme-user-colors-2: #f4f40b;
--theme-user-colors-4: #58f40b; --theme-user-colors-3: #58f40b;
--theme-user-colors-5: #0bf4f4; --theme-user-colors-4: #0bf4f4;
--theme-user-colors-6: #0ba6f4; --theme-user-colors-5: #0ba6f4;
--theme-user-colors-7: #6666ff; --theme-user-colors-6: #6666ff;
--theme-user-colors-8: #f40bf4; --theme-user-colors-7: #f40bf4;
--owncast-purple: #00ff00; --owncast-purple: #00ff00;
--owncast-purple-25: rgba(120, 113, 255, 0.25); --owncast-purple-25: rgba(120, 113, 255, 0.25);
--owncast-purple-50: rgba(120, 113, 255, 0.5); --owncast-purple-50: rgba(120, 113, 255, 0.5);
@ -71,10 +67,6 @@
--color-owncast-background-primary: #fffcf2; --color-owncast-background-primary: #fffcf2;
--color-owncast-background-secondary: #f0efe4; --color-owncast-background-secondary: #f0efe4;
--rounded-corners: 0.5em; --rounded-corners: 0.5em;
--font-owncast-body: 'Open Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, --font-owncast-body: 'Open Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', --font-owncast-display: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
'Segoe UI Symbol', 'Noto Color Emoji';
--font-owncast-display: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
} }