SMYN: New styles + broader capability. (#1298)

* New Styles.

* More locations, DM non-hover styling, etc.

* Corrections.

* More Options.

* Fixes.

* Fixes.
This commit is contained in:
Etorix 2026-08-15 07:23:10 -07:00 • committed by GitHub
parent 4bcb1dca36
commit ca0bd2c129
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 454 additions and 406 deletions

View file

@ -9,7 +9,7 @@ import "./style.css";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { DataStore } from "@api/index";
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings, migratePluginSetting } from "@api/Settings";
import { TextButton } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
@ -19,13 +19,36 @@ import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/index";
import definePlugin, { OptionType } from "@utils/types";
import { GuildMember, Message, RenderModalProps, User } from "@vencord/discord-types";
import { findByCodeLazy } from "@webpack";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { AccessibilityStore, ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, Modal, openModal, RelationshipStore, StreamerModeStore, TextInput, useEffect, UserStore, useState } from "@webpack/common";
import { JSX } from "react";
const SMYNC = classNameFactory();
const wrapEmojis = findByCodeLazy("lastIndex;return");
const adjustColor = findByCodeLazy("light1", '.get("hsl.s"))');
interface DisplayNameStyles {
colors: number[];
effectId: number;
fontId: number;
}
interface UserNameWithEffectsProps {
userName: string;
displayNameStyles: DisplayNameStyles;
effectDisplayType: number;
textClassName?: string;
loop?: boolean;
shouldWrap?: boolean;
}
const UserNameWithEffects = findComponentByCodeLazy<UserNameWithEffectsProps>(
"UserNameWithEffects",
"--custom-display-name-styles-prism-cycle"
);
const DisplayNameEffectDisplayTypes: Record<"PLAIN" | "STATIC" | "ANIMATED", number> = findByPropsLazy("PLAIN", "STATIC", "ANIMATED");
const DisplayNameEffects: Record<"GRADIENT" | "GLOW" | "POP" | "GUMMY", number> = findByPropsLazy("SOLID", "GRADIENT", "NEON", "TOON", "POP", "GLOW", "PRISM", "GUMMY");
const DisplayNameFonts: Record<"DEFAULT", number> = findByPropsLazy("DEFAULT", "CHERRY_BOMB", "CHICLE", "MUSEO_MODERNO");
const roleColorPattern = /^role((?:\+|-)\d{0,4})?$/iu;
const symbolPattern = /^[\p{S}\p{P}]{1,3}$/iu;
@ -106,12 +129,12 @@ function validColor(color: string) {
function resolveColor(
colorStrings: colorStringsType,
displayNameStyles: { effectId: number; colors: number[]; } | null | undefined,
displayNameStyles: DisplayNameStyles | null | undefined,
savedColor: string,
canUseGradient: boolean,
inGuild: boolean,
ircColorsEnabled: boolean,
isHovering: boolean,
shouldShowEffects: boolean,
): Record<string, any> | null {
const defaultColor = getComputedStyle(document.documentElement).getPropertyValue("--text-strong").trim() || null;
@ -119,7 +142,11 @@ function resolveColor(
savedColor = savedColor.trim() || defaultColor;
const isRoleColor = savedColor.toLowerCase().includes("role");
const forceDefault = !inGuild && !ircColorsEnabled && (isRoleColor ? !isHovering : false);
const forceDefault = !inGuild
&& !ircColorsEnabled
&& !displayNameStyles
&& isRoleColor
&& !shouldShowEffects;
let gradient: any = null;
let primaryColor: any = null;
@ -327,6 +354,13 @@ interface memberListProfileReactionProps {
type: "typingIndicator" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel";
guildId?: string;
tags?: any;
isHovered?: boolean;
}
interface activeNowNameProps {
user: User | null | undefined;
guildId?: string;
isHovered: boolean;
}
type colorStringsType = { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null | undefined;
@ -341,7 +375,7 @@ function getTypingMemberListProfilesReactionsVoiceName(
const member = guildId && user ? GuildMemberStore.getMember(guildId, user.id) : null;
const author = user && member ? { ...user, ...member } : user || member || null;
const shouldHookless = ["typingIndicator", "reactionsTooltip", "profilesTooltip"].includes(type);
return renderUsername(author, null, null, type, "", shouldHookless, !!guildId);
return renderUsername(author, null, null, type, "", shouldHookless, !!guildId, undefined, undefined, props.isHovered);
}
function getTypingMemberListProfilesReactionsVoiceNameText(props: memberListProfileReactionProps): string | null {
@ -352,6 +386,26 @@ function getTypingMemberListProfilesReactionsVoiceNameElement(props: memberListP
return getTypingMemberListProfilesReactionsVoiceName(props)[1];
}
function getActiveNowNameElement({ user, guildId, isHovered }: activeNowNameProps): JSX.Element | string | null {
const name = getTypingMemberListProfilesReactionsVoiceNameText({ user, guildId, type: "membersList" });
if (!name) return null;
const displayNameStyles: DisplayNameStyles | null = (
AccessibilityStore.displayNameStylesEnabled
&& (user as any)?.displayNameStyles
) || null;
return displayNameStyles
? <DisplayNameEffectName
name={name}
styles={displayNameStyles}
animate={shouldAnimateNameEffects(isHovered, settings.store.styleActiveNow)}
showStaticEffect={settings.store.styleActiveNow}
/>
: name;
}
function getMessageName(props: messageProps): [string | null, JSX.Element | null, string | null] {
const { hideDefaultAtSign, replies } = settings.use(["hideDefaultAtSign", "replies"]);
const { message, userOverride, isRepliedMessage, withMentionPrefix } = props;
@ -397,40 +451,96 @@ function getMentionNameElement(props: mentionProps): JSX.Element | null {
return renderUsername(author, channelId || null, nestedProps?.messageId || null, "mentions", mentionSymbol, false, !!channel?.guild_id, colorString, colorStrings)[1];
}
function getEffectType(effectId: number | null | undefined): string | null {
switch (effectId) {
case 1: return "solid";
// Delegate gradient effect handling to the guild
// gradient handler. This adds animation to DM
// gradients which are usually static.
// case 2: return "gradient";
case 3: return "neon";
case 4: return "toon";
case 5: return "pop";
default: return null;
}
function getDisplayNameStyles(styles: DisplayNameStyles, ignoreFont: boolean): DisplayNameStyles {
return ignoreFont
? { ...styles, fontId: DisplayNameFonts.DEFAULT }
: styles;
}
function computeEffectCSSVars(styles: any): Record<string, string> {
if (!styles?.colors?.length) return {};
function getDisplayNameEffectDisplayType(isHovered: boolean, showStaticEffect = true): number {
if (shouldAnimateNameEffects(isHovered, showStaticEffect)) return DisplayNameEffectDisplayTypes.ANIMATED;
const toHex = (n: number) => `#${(n >>> 0).toString(16).padStart(6, "0")}`;
const primary = toHex(styles.colors[0]);
const secondary = styles.colors.length > 1 ? toHex(styles.colors[1]) : primary;
const adjusted = adjustColor(primary);
return showStaticEffect
? DisplayNameEffectDisplayTypes.STATIC
: DisplayNameEffectDisplayTypes.PLAIN;
}
return {
"--smyn-effect-main-color": adjusted.main,
"--smyn-effect-gradient-start": primary,
"--smyn-effect-gradient-end": secondary,
"--smyn-effect-light-1": adjusted.light1,
"--smyn-effect-light-2": adjusted.light2,
"--smyn-effect-dark-1": adjusted.dark1,
"--smyn-effect-dark-2": adjusted.dark2,
"--smyn-effect-neon-stroke": adjusted.neonStroke,
"--smyn-effect-neon-flicker": `hsl(from ${adjusted.main} h calc(min(1, s) * ((s * 1.1) + 10)) 85)`,
"--smyn-effect-toon-stroke": adjusted.toonStroke,
};
function shouldAnimateNameEffects(isHovered: boolean, isEffectVisible = true): boolean {
return isHovered || (isEffectVisible && settings.store.alwaysAnimateEffects);
}
function getDisplayNameEffectClassName(
styles: DisplayNameStyles | null | undefined,
effectDisplayType: number,
): string {
const useGradientAnimationOverride = needsGradientAnimationOverride(styles)
&& effectDisplayType === DisplayNameEffectDisplayTypes.ANIMATED
&& !AccessibilityStore.useReducedMotion;
return [
"smyn-native-effect",
useGradientAnimationOverride && "smyn-native-gradient-animated",
styles?.effectId === DisplayNameEffects.GRADIENT && settings.store.gradientGlow && "smyn-native-gradient-glow",
useGradientAnimationOverride && styles?.effectId === DisplayNameEffects.GRADIENT && settings.store.gradientGlow && "smyn-native-gradient-glow-active",
styles?.effectId === DisplayNameEffects.POP && "smyn-native-pop",
styles?.effectId === DisplayNameEffects.GUMMY && "smyn-native-gummy",
styles?.effectId === DisplayNameEffects.GUMMY
&& effectDisplayType === DisplayNameEffectDisplayTypes.ANIMATED
&& !AccessibilityStore.useReducedMotion
&& "smyn-native-gummy-animated",
].filter(Boolean).join(" ");
}
function DisplayNameEffectName({ name, styles, animate, ignoreFont = false, showStaticEffect = true, useMessageLayout = false }: {
name: string;
styles: DisplayNameStyles;
animate: boolean;
ignoreFont?: boolean;
showStaticEffect?: boolean;
useMessageLayout?: boolean;
}) {
const effectDisplayType = animate
? DisplayNameEffectDisplayTypes.ANIMATED
: showStaticEffect
? DisplayNameEffectDisplayTypes.STATIC
: DisplayNameEffectDisplayTypes.PLAIN;
return (
<UserNameWithEffects
userName={name}
displayNameStyles={getDisplayNameStyles(styles, ignoreFont)}
effectDisplayType={effectDisplayType}
textClassName={SMYNC(getDisplayNameEffectClassName(styles, effectDisplayType), {
"smyn-native-message-effect": useMessageLayout,
})}
loop
/>
);
}
function needsGradientAnimationOverride(styles: DisplayNameStyles | null | undefined): boolean {
return styles?.effectId === DisplayNameEffects.GRADIENT
|| styles?.effectId === DisplayNameEffects.GLOW;
}
function getSecondaryNameStyle(
style: Record<string, any> | null,
useNativeEffect: boolean,
ignoreEffects: boolean,
animateGradient: boolean,
showGradientGlow: boolean,
): Record<string, any> {
if (!style) return {};
if (useNativeEffect) {
if (!showGradientGlow || !style.gradient) return {};
return animateGradient ? style.gradient.animated : style.gradient.static.original;
}
if (ignoreEffects) return style.normal.adjusted;
if (animateGradient && style.gradient) return style.gradient.animated;
return style.gradient?.static.original ?? style.normal.adjusted;
}
function renderUsername(
@ -442,7 +552,8 @@ function renderUsername(
hookless: boolean,
inGuild: boolean,
colorString?: string,
colorStrings?: { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null
colorStrings?: { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null,
isHoveredOverride?: boolean,
): [string | null, JSX.Element | null, string | null] {
const isMessage = type === "messages";
const isReply = type === "replies";
@ -455,20 +566,23 @@ function renderUsername(
const isReaction = isReactionsTooltip || isReactionsPopout;
const isVoice = type === "voiceChannel";
const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "typingIndicator", "memberList", "profilePopout", "reactions", "friendNameOnlyInDirectMessages", "customNameOnlyInDirectMessages", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]);
const { messages, replies, mentions, typingIndicator, memberList, profilePopout, reactions, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages, discriminators, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config;
const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "typingIndicator", "memberList", "styleDirectMessagesList", "styleDirectMessagesMessages", "styleFriendsList", "styleActiveNow", "profilePopout", "reactions", "friendNameOnlyInDirectMessages", "customNameOnlyInDirectMessages", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreEffects", "ignoreFonts", "animateEffects", "alwaysAnimateEffects", "gradientGlow", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]);
const { messages, replies, mentions, typingIndicator, memberList, styleDirectMessagesMessages, profilePopout, reactions, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages, discriminators, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreEffects, ignoreFonts, animateEffects, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config;
const channel = channelId ? ChannelStore.getChannel(channelId) || null : null;
const message = channelId && messageId ? MessageStore.getMessage(channelId, messageId) : null;
const groupId = (message as any)?.showMeYourNameGroupId || null;
const showStaticDisplayNameEffect = !isMessage
|| !(channel?.isDM() || channel?.isGroupDM())
|| styleDirectMessagesMessages;
const isHovering = (isMessage || isMention)
? ((messageId && hoveringMessageMap.has(messageId)) || (groupId && hoveringMessageMap.has(groupId)))
const isHovered = isHoveredOverride ?? ((isMessage || isMention)
? Boolean((messageId && hoveringMessageMap.has(messageId)) || (groupId && hoveringMessageMap.has(groupId)))
: isReply
? (messageId && hoveringRepliesMap.has(messageId)) || (groupId && hoveringRepliesMap.has(groupId))
: isReactionsPopout
? hoveringReactionPopoutSet.has((author as User).id)
: false;
? Boolean((messageId && hoveringRepliesMap.has(messageId)) || (groupId && hoveringRepliesMap.has(groupId)))
: false);
const shouldShowHoverEffects = shouldAnimateNameEffects(isHovered, showStaticDisplayNameEffect);
const shouldShowDisplayNameStyle = showStaticDisplayNameEffect || shouldShowHoverEffects;
if (colorString && !colorStrings) {
colorStrings = {
@ -481,26 +595,27 @@ function renderUsername(
const ircColorsEnabled = isPluginEnabled(ircColors.name);
const authorColorStrings = colorStrings || (author as any)?.colorStrings || null;
const authorDisplayNameStyles = (!inGuild && !ircColorsEnabled && (author as any)?.displayNameStyles) || null;
const effectType = authorDisplayNameStyles ? getEffectType(authorDisplayNameStyles.effectId) : null;
const effectCSSVars = authorDisplayNameStyles ? computeEffectCSSVars(authorDisplayNameStyles) : {};
const hasEffect = !!effectType;
const needsEffectDataAttr = effectType === "neon" || effectType === "toon" || effectType === "pop";
const shouldShowEffect = hasEffect && isHovering;
const shouldAnimateEffect = shouldShowEffect && !AccessibilityStore.useReducedMotion;
const authorDisplayNameStyles: DisplayNameStyles | null = (
!inGuild
&& !ircColorsEnabled
&& AccessibilityStore.displayNameStylesEnabled
&& (author as any)?.displayNameStyles
) || null;
const shouldShowDisplayNameEffect = !!authorDisplayNameStyles;
const usesGradientAnimationOverride = needsGradientAnimationOverride(authorDisplayNameStyles);
const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : !inGuild);
const useTopRoleStyle = isMention || isReactionsPopout || channel?.isDM() || channel?.isGroupDM();
const topRoleStyle = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, "Role", canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const topRoleStyle = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, "Role", canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const hasGradient = !!topRoleStyle?.gradient && Object.keys(topRoleStyle.gradient).length > 0;
const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d";
const options = splitTemplate(includedNames);
const resolvedUsernameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, usernameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const resolvedDisplayNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, displayNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const resolvedNicknameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, nicknameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const resolvedFriendNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, friendNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const resolvedCustomNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, customNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, isHovering) : null;
const resolvedUsernameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, usernameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const resolvedDisplayNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, displayNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const resolvedNicknameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, nicknameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const resolvedFriendNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, friendNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const resolvedCustomNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, customNameColor.trim(), canUseGradient, inGuild, ircColorsEnabled, shouldShowHoverEffects) : null;
const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" };
const { username, display, nick, friend, custom } = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators, inGuild, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages);
@ -634,19 +749,22 @@ function renderUsername(
fourth = remainingNames.shift();
fifth = remainingNames.shift();
const shouldGradientGlow = isHovering && hasGradient;
const shouldAnimateGradients = shouldGradientGlow && !AccessibilityStore.useReducedMotion;
const shouldAnimateSecondaryNames = animateGradients && !ignoreGradients;
const shouldShowGradientGlow = shouldShowHoverEffects
&& hasGradient
&& (!authorDisplayNameStyles || (usesGradientAnimationOverride && authorDisplayNameStyles.effectId !== DisplayNameEffects.GRADIENT));
const shouldAnimatePrimaryGradient = shouldShowGradientGlow && !AccessibilityStore.useReducedMotion;
const shouldAnimateSecondaryEffects = shouldShowHoverEffects && animateEffects && !ignoreEffects;
const shouldShowSecondaryDisplayNameEffect = shouldShowDisplayNameEffect && !ignoreEffects;
const firstDataText = mentionSymbol + first.name;
const secondDataText = second && shouldAnimateSecondaryNames ? second.name : "";
const thirdDataText = third && shouldAnimateSecondaryNames ? third.name : "";
const fourthDataText = fourth && shouldAnimateSecondaryNames ? fourth.name : "";
const fifthDataText = fifth && shouldAnimateSecondaryNames ? fifth.name : "";
const secondDataText = second && shouldAnimateSecondaryEffects ? second.name : "";
const thirdDataText = third && shouldAnimateSecondaryEffects ? third.name : "";
const fourthDataText = fourth && shouldAnimateSecondaryEffects ? fourth.name : "";
const fifthDataText = fifth && shouldAnimateSecondaryEffects ? fifth.name : "";
const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText, fifthDataText].filter(Boolean).join(nameSeparator).trim();
// Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages.
const hoveringClass = (isHovering ? " smyn-gradient-hovered" : "");
const hoveringClass = (shouldShowHoverEffects ? " smyn-gradient-hovered" : "");
const gradientClasses = useTopRoleStyle
? "smyn-gradient smyn-gradient-inherit-bg" + hoveringClass
: "smyn-gradient smyn-gradient-unset-bg" + hoveringClass;
@ -671,43 +789,51 @@ function renderUsername(
...(isReactionsPopout
? { display: "flex", flexWrap: "wrap", lineHeight: "1.1em", fontSize: "0.9em" }
: {}),
...(hasEffect ? effectCSSVars : {}),
"--smyn-gradient-duration": `${animationDuration}s`
"--smyn-gradient-duration": `${animationDuration}s`,
"--smyn-underline-color": topRoleStyle?.normal.original?.["text-decoration-color"],
} as React.CSSProperties;
const nameElement = (
<span
style={{
...topLevelStyle,
...(topRoleStyle?.normal.original || {})
...(shouldShowDisplayNameEffect ? {} : topRoleStyle?.normal.original || {})
}}
className="smyn-container"
>
{mentionSymbol && <span>{mentionSymbol}</span>}
{(
<span
className={SMYNC(firstGroupClasses, { [gradientClasses]: shouldGradientGlow })}
data-text={shouldGradientGlow ? firstDataText : undefined}
style={(shouldGradientGlow && useTopRoleStyle && topRoleStyle ? topRoleStyle.gradient.animated : undefined) as React.CSSProperties}
className={SMYNC(firstGroupClasses, { [gradientClasses]: shouldShowGradientGlow })}
data-text={shouldShowGradientGlow ? firstDataText : undefined}
style={(shouldShowGradientGlow && useTopRoleStyle && topRoleStyle?.gradient
? shouldAnimatePrimaryGradient
? topRoleStyle.gradient.animated
: topRoleStyle.gradient.static.original
: undefined) as React.CSSProperties}
>
<span
className={SMYNC(firstNameClasses, {
"smyn-effect-container": shouldShowEffect,
[`smyn-effect-${effectType}`]: shouldShowEffect,
"smyn-effect-animated": shouldAnimateEffect
})}
data-username-with-effects={needsEffectDataAttr && shouldShowEffect ? first.name : undefined}
style={shouldShowEffect
className={SMYNC(firstNameClasses)}
style={shouldShowDisplayNameEffect
? undefined
: topRoleStyle ?
shouldAnimateGradients && topRoleStyle.gradient
shouldAnimatePrimaryGradient && topRoleStyle.gradient
? topRoleStyle.gradient.animated
: topRoleStyle.gradient
? topRoleStyle.gradient.static.original
: topRoleStyle.normal.original
: undefined
}>
{first.wrapped}</span>
{shouldShowDisplayNameEffect && authorDisplayNameStyles
? <DisplayNameEffectName
name={first.name}
styles={authorDisplayNameStyles}
animate={shouldShowHoverEffects}
showStaticEffect={showStaticDisplayNameEffect}
useMessageLayout={isMessage}
/>
: first.wrapped}
</span>
</span>
)}
{[
@ -726,26 +852,30 @@ function renderUsername(
<span
// On non-primary names, allow disabling the effects completely, or just their animation & glow.
className={SMYNC(nameClass, {
[gradientClasses]: shouldGradientGlow && shouldAnimateSecondaryNames,
"smyn-effect-container": shouldShowEffect && !ignoreGradients,
[`smyn-effect-${effectType}`]: shouldShowEffect && !ignoreGradients,
"smyn-effect-animated": shouldAnimateEffect && shouldAnimateSecondaryNames
[gradientClasses]: shouldShowGradientGlow && shouldAnimateSecondaryEffects,
})}
data-text={shouldGradientGlow && dataText ? dataText : undefined}
data-username-with-effects={needsEffectDataAttr && shouldShowEffect && !ignoreGradients ? name.name : undefined}
data-text={shouldShowGradientGlow && dataText ? dataText : undefined}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(name.style
? ignoreGradients
? name.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && name.style.gradient
? name.style.gradient.animated
: name.style.gradient
? name.style.gradient.static.original
: name.style.normal.adjusted
: {})
...(ignoreFonts ? { fontFamily: "var(--font-primary)", letterSpacing: "normal" } : {}),
...getSecondaryNameStyle(
shouldShowDisplayNameStyle ? name.style : null,
shouldShowSecondaryDisplayNameEffect,
ignoreEffects,
shouldAnimatePrimaryGradient && shouldAnimateSecondaryEffects,
shouldShowGradientGlow && shouldAnimateSecondaryEffects,
)
}}>
{name.wrapped}</span>
{shouldShowSecondaryDisplayNameEffect && authorDisplayNameStyles
? <DisplayNameEffectName
name={name.name}
styles={authorDisplayNameStyles}
animate={shouldAnimateSecondaryEffects}
ignoreFont={ignoreFonts}
showStaticEffect={showStaticDisplayNameEffect}
useMessageLayout={isMessage}
/>
: name.wrapped}
</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{name.suffix}</span>
</span>
@ -758,28 +888,25 @@ function renderUsername(
const hoveringMessageMap = new Map<string, number>();
const hoveringRepliesMap = new Map<string, number>();
const hoveringReactionPopoutSet = new Set<string>();
function handleHoveringMessage(message: any, isHovering: boolean) {
function handleHoveringMessage(message: any, isHovered: boolean) {
const messageId = message?.id;
const repliedId = message?.messageReference?.message_id;
const groupId = message?.showMeYourNameGroupId ?? "";
const effectiveIsHovering = settings.store.alwaysShowEffects || isHovering;
useEffect(() => {
if (!message) return;
if (!message || !isHovered) return;
if (effectiveIsHovering) {
addHoveringMessage(messageId);
addHoveringMessage(groupId);
addHoveringReply(repliedId);
} else {
addHoveringMessage(messageId);
addHoveringMessage(groupId);
addHoveringReply(repliedId);
return () => {
removeHoveringMessage(messageId);
removeHoveringMessage(groupId);
removeHoveringReply(repliedId);
}
}, [messageId, groupId, effectiveIsHovering]);
};
}, [messageId, repliedId, groupId, isHovered]);
}
function addHoveringMessage(id: string) {
@ -789,7 +916,7 @@ function addHoveringMessage(id: string) {
hoveringMessageMap.set(id, currentCount + 1);
if (currentCount === 0) {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
}
}
@ -800,7 +927,7 @@ function removeHoveringMessage(id: string) {
if (currentCount <= 1) {
hoveringMessageMap.delete(id);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
} else {
hoveringMessageMap.set(id, currentCount - 1);
}
@ -813,7 +940,7 @@ function addHoveringReply(id: string) {
hoveringRepliesMap.set(id, currentCount + 1);
if (currentCount === 0) {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
}
}
@ -824,19 +951,17 @@ function removeHoveringReply(id: string) {
if (currentCount <= 1) {
hoveringRepliesMap.delete(id);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
} else {
hoveringRepliesMap.set(id, currentCount - 1);
}
}
function addHoveringReactionPopout(id: string) {
hoveringReactionPopoutSet.add(id);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
function useNameHoverState() {
return useState(false);
}
function removeHoveringReactionPopout(id: string) {
hoveringReactionPopoutSet.delete(id);
function triggerNameRerender() {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}
@ -862,7 +987,7 @@ function CustomNicknameModal({ modalProps, user }: { modalProps: RenderModalProp
}
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
modalProps.onClose();
}
},
@ -893,7 +1018,7 @@ function CustomNicknameModal({ modalProps, user }: { modalProps: RenderModalProp
setValue("");
delete customNicknames[user.id];
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
triggerNameRerender();
}}
>
Reset SMYN Nickname
@ -925,6 +1050,10 @@ const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => {
);
};
migratePluginSetting("ShowMeYourName", "ignoreEffects", "ignoreGradients");
migratePluginSetting("ShowMeYourName", "animateEffects", "animateGradients");
migratePluginSetting("ShowMeYourName", "alwaysAnimateEffects", "alwaysShowEffects");
const settings = definePluginSettings({
messages: {
type: OptionType.BOOLEAN,
@ -949,7 +1078,31 @@ const settings = definePluginSettings({
memberList: {
type: OptionType.BOOLEAN,
default: true,
description: "Display the first available name listed in your custom name format in the members list, DMs list, and friends list.",
description: "Display the first available name listed in your custom name format in the members list, DMs list, friends list, and Active Now.",
},
styleDirectMessagesList: {
type: OptionType.BOOLEAN,
default: true,
displayName: "Style Direct Messages List",
description: "Show display name effects on unselected entries without requiring hover. Selected and hovered entries are still styled when disabled.",
},
styleDirectMessagesMessages: {
type: OptionType.BOOLEAN,
default: true,
displayName: "Style Direct Messages Messages",
description: "Show display name effects on message authors without requiring hover. Hovered names are still styled when disabled.",
},
styleFriendsList: {
type: OptionType.BOOLEAN,
default: true,
displayName: "Style Friends List",
description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.",
},
styleActiveNow: {
type: OptionType.BOOLEAN,
default: true,
displayName: "Style Active Now",
description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.",
},
profilePopout: {
type: OptionType.BOOLEAN,
@ -991,15 +1144,26 @@ const settings = definePluginSettings({
default: false,
description: "For the non-primary names, use Discord's default fonts regardless of the user's custom nitro font.",
},
ignoreGradients: {
ignoreEffects: {
type: OptionType.BOOLEAN,
default: true,
description: "For the non-primary names, if the role has a gradient and the color below is set to \"Role+-#\", use the primary color instead of the whole gradient, and if it has a nitro effect, ignore it entirely."
description: "For non-primary names, use only the primary role color and omit display name effects.",
},
animateGradients: {
animateEffects: {
type: OptionType.BOOLEAN,
default: false,
description: "For the non-primary names, if the role has a gradient or nitro effect, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.",
description: "Animate role gradients and display name effects on non-primary names. This is disabled by \"Ignore Effects\" and reduced motion.",
},
alwaysAnimateEffects: {
type: OptionType.BOOLEAN,
default: false,
displayName: "Always Animate Effects",
description: "Force display name effects to animate when they would already be visible, even without hover.",
},
gradientGlow: {
type: OptionType.BOOLEAN,
default: true,
description: "Add a glow to animated user gradients the same way server role gradients glow.",
},
nameSeparator: {
type: OptionType.STRING,
@ -1016,11 +1180,6 @@ const settings = definePluginSettings({
default: false,
description: "Only display custom names when in DMs, and not in servers.",
},
alwaysShowEffects: {
type: OptionType.BOOLEAN,
default: false,
description: "Always show effects as if you were hovering.",
},
includedNames: {
type: OptionType.STRING,
description: "The order to display usernames, display names, nicknames, friend names, and custom names. Use the following placeholders: {user}, {display}, {nick}, {friend}, {custom}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {custom, friend, nick}. You can have up to three prefixes and three suffixes per name.",
@ -1076,6 +1235,7 @@ export default definePlugin({
patches: [
{
// Replace message and reply names and expose their hover context.
find: '="SYSTEM_TAG"',
group: true,
replacement: [
@ -1085,7 +1245,7 @@ export default definePlugin({
replace: "$3$self.getMessageNameElement({...arguments[0],colorString:$1,colorStrings:$2})??($4),\"data-text\":$self.getMessageNameText(arguments[0])??($5)"
},
{
// Pass the message object to the should-animate checker.
// Expose the message object to SMYN's message-hover patch.
match: /(\(\{)(shouldSubscribe)/,
replace: "$1message:arguments[0].message,$2"
}
@ -1100,29 +1260,94 @@ export default definePlugin({
},
},
{
// Replace names in DMs list.
// Replace names and preserve display name styles in the DMs list.
find: "ImpressionNames.DM_LIST_RIGHT_CLICK_MENU_SHOWN",
replacement: {
match: /(?<=getMentionCount\(\i.id\)>0\),\i=)/,
replace: "$self.getTypingMemberListProfilesReactionsVoiceNameText({...arguments[0],type:\"membersList\"})??"
},
group: true,
replacement: [
{
// Replace the displayed DM name.
match: /(?<=getMentionCount\(\i.id\)>0\),\i=)/,
replace: "$self.getTypingMemberListProfilesReactionsVoiceNameText({...arguments[0],type:\"membersList\"})??"
},
{
// Keep fonts at rest and gate persistent effects behind the location setting.
match: /(?<=userName:\i,)displayNameStyles:(\i)\?\.displayNameStyles,effectDisplayType:(\i\|\|\i\|\|\i)\?\i\.\i\.ANIMATED:\i\.\i\.PLAIN,loop:\i(?=,boldFontOpacity:)/,
replace: "displayNameStyles:$1?.displayNameStyles,effectDisplayType:$self.getDisplayNameEffectDisplayType($2,$self.settings.store.styleDirectMessagesList),textClassName:$self.getDisplayNameEffectClassName($1?.displayNameStyles,$self.getDisplayNameEffectDisplayType($2,$self.settings.store.styleDirectMessagesList)),loop:$self.shouldAnimateNameEffects($2,$self.settings.store.styleDirectMessagesList)"
}
],
},
{
// Replace names in the friends list.
find: "hasUniqueUsername()}),usernameClass",
group: true,
replacement: [
{
// Pass the row hover state to Discord's native display-name component.
match: /(?<=\(0,\i\.jsx\)\(\i\.\i,\{user:\i,nick:\i,)(?=botClass:)/,
replace: "displayNameStylesType:$self.getDisplayNameEffectDisplayType(arguments[0].hovered,$self.settings.store.styleFriendsList),"
},
{
// Replace the displayed friend name.
match: /(?<=nick:)(\i)/,
replace: "$self.getTypingMemberListProfilesReactionsVoiceNameText({user:arguments[0].user,type:\"membersList\"})??$1"
}
],
},
{
// Set Discord's existing friends-row hover state on pointer entry.
find: "handleMouseEnter=()=>{let{isFocused:",
replacement: {
match: /(?<=nick:)(\i)/,
replace: "$self.getTypingMemberListProfilesReactionsVoiceNameText({user:arguments[0].user,type:\"membersList\"})??$1"
match: /(?<=handleMouseEnter=\(\)=>\{let\{isFocused:\i,isActive:\i,onOtherHover:\i}=this.props,\{isContextMenuActive:\i}=this.state;this.setState\(\{hovered:)\i(?=}\),)/,
replace: "!0"
},
},
{
// Don't block name style in friends list just
// because the name is the same as the username.
// Preserve display name styles when SMYN replaces the friends-list name.
find: 'location:"DiscordTag"})',
replacement: {
match: /(?<=,forceUsername:(\i),.*?displayNameStyles:)\i!==\i\?(\i.displayNameStyles):null/,
replace: "!$1?$2:null"
},
group: true,
replacement: [
{
// Keep the user's display name styles when SMYN supplies the name.
match: /(?<=showStreamerModeTooltip:\i&&\i\.\i\.isNameConcealed\(e\),displayNameStyles:)\i!==\i\?(\i\.displayNameStyles):null/,
replace: "!arguments[0].forceUsername?$1:null"
},
{
// Render native display name effects statically before hover.
match: /(?<=displayNameStylesType:\i=\i\.\i\.)PLAIN(?=,\.\.\.\i}=e)/,
replace: "STATIC"
},
{
// Attach SMYN's effect-specific classes.
match: /(?<=userName:\i,displayNameStyles:(\i),effectDisplayType:(\i))(?=}\):\i)/,
replace: ",textClassName:$self.getDisplayNameEffectClassName($1,$2)"
}
],
},
{
find: '("NowPlayingHeader")',
group: true,
replacement: [
{
// Replace the card title and apply its propagated hover state.
match: /(?<=\.Header,\{priorityUser:(\i),guildId:(\i)\?\.id,title:)(\i)(?=,subtitle:.{0,200}?displayNameFont:1===\i\.length)/,
replace: "$self.getActiveNowNameElement({user:$1.user,guildId:$2?.id,isHovered:arguments[0].smynHovered})??$3"
},
{
// Add local hover state to each Active Now card.
match: /(hasStaffQuestActivityPanelOverride:\i}=\i,\i=(\i)\.useRef\(null\),\[\i,\i\]=\2\.useState\(null\))/,
replace: "$1,[smynHovered,setSmynHovered]=$2.useState(!1)"
},
{
// Pass the card hover state to its title renderer.
match: /(?<=\(0,\i\.jsx\)\(\i,\{party:\i,onUserContextMenu:\i,quest:\i)(?=}\))/,
replace: ",smynHovered"
},
{
// Update local hover state while preserving Discord's handlers.
match: /(?<=\{\.\.\.(\i),ref:\i,)onMouseEnter:(\i)(?=,"aria-haspopup":"menu",className:)/,
replace: "onMouseEnter:smynEvent=>{$2(smynEvent);setSmynHovered(!0)},onMouseLeave:smynEvent=>{$1.onMouseLeave?.(smynEvent);setSmynHovered(!1)}"
}
],
},
{
// Replace name in solo DM title bar and tooltip.
@ -1133,8 +1358,8 @@ export default definePlugin({
},
},
{
// Track hovering on messages to animate gradients.
// Attach the group ID to their messages to allow animating gradients within a group.
// Track message hover to animate display name effects.
// Attach the group ID so every name in a grouped message animates together.
find: "CUSTOM_GIFT?\"\":",
replacement: [
{
@ -1146,6 +1371,7 @@ export default definePlugin({
{
// Replace names in mentions.
find: ".USER_MENTION)",
group: true,
replacement: [
{
match: /(?=function \i\(\i\){return\(0)/,
@ -1208,18 +1434,24 @@ export default definePlugin({
}
},
{
// Replace reaction-popout names and track hover per reactor row.
find: ".MESSAGE,userId:",
group: true,
replacement: [
{
// Track hovering over reaction popouts.
// Add local hover state to each reactor row.
match: /(function \i\(\i\)\{let\{emoji:\i,user:\i,message:\i,channel:\i,guildId:\i,reactionType:\i,onRemoveReactor:\i\}=\i,)/,
replace: "$1[smynHovered,setSmynHovered]=$self.useNameHoverState(),"
},
{
// Update hover state from the full reactor row.
match: /(?<=\(0,\i.\i\)\(\i.\i,{className:\i.\i,)(?=(?:align:\i\.\i\.\i\.CENTER|onContextMenu:\i=>))/g,
replace: "onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},"
replace: "onMouseEnter:()=>setSmynHovered(!0),onMouseLeave:()=>setSmynHovered(!1),"
},
{
// Replace names in reaction popouts.
match: /(?<=Child,{className:\i.\i,children:)/g,
replace: "($self.getTypingMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\"}))??"
replace: "($self.getTypingMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\",isHovered:smynHovered}))??"
}
]
},
@ -1260,36 +1492,25 @@ export default definePlugin({
},
flux: {
RELATIONSHIP_UPDATE(data) {
// Allows rerendering when changing friend names.
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
},
RUNNING_STREAMER_TOOLS_CHANGE(data) {
// Allows rerendering when toggling streamer mode.
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
},
ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED(data) {
// Allows rerendering when toggling reduced motion.
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
},
ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION(data) {
// Allows rerendering when toggling reduced motion.
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}
RELATIONSHIP_UPDATE: triggerNameRerender,
RUNNING_STREAMER_TOOLS_CHANGE: triggerNameRerender,
ACCESSIBILITY_SET_DISPLAY_NAME_STYLES_ENABLED: triggerNameRerender,
ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED: triggerNameRerender,
ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION: triggerNameRerender,
},
addHoveringMessage,
removeHoveringMessage,
handleHoveringMessage,
addHoveringReactionPopout,
removeHoveringReactionPopout,
useNameHoverState,
getMessageName,
getMessageNameText,
getMessageNameElement,
getMentionNameElement,
getActiveNowNameElement,
getDisplayNameEffectClassName,
getDisplayNameEffectDisplayType,
shouldAnimateNameEffects,
getTypingMemberListProfilesReactionsVoiceNameText,
getTypingMemberListProfilesReactionsVoiceNameElement
});

View file

@ -54,263 +54,90 @@
font-weight: 500;
}
.smyn-container:hover {
text-decoration: underline;
.smyn-native-gradient-animated > [data-username-with-effects] {
animation: smyn-animation var(--smyn-gradient-duration, 1.5s) linear infinite;
background-image: linear-gradient(to right, var(--custom-display-name-styles-gradient-stops), var(--custom-display-name-styles-main-color) 100%) !important;
background-size: 100px auto !important;
}
.smyn-effect-container {
position: relative;
opacity: 1;
}
.smyn-effect-solid {
color: var(--smyn-effect-main-color);
}
.smyn-effect-gradient {
background: linear-gradient(to bottom right, var(--smyn-effect-gradient-start) 10%, var(--smyn-effect-gradient-end) 90%);
.smyn-native-gradient-glow > [data-username-with-effects]::after {
background: inherit;
background-clip: text;
-webkit-background-clip: text;
background-size: 100% auto;
-webkit-text-fill-color: transparent;
position: relative;
z-index: 0;
}
.smyn-effect-neon {
paint-order: stroke fill;
-webkit-text-stroke-width: calc(1px + .04em);
-webkit-text-stroke-color: var(--smyn-effect-neon-stroke);
-webkit-text-fill-color: currentcolor;
color: var(--white);
margin-inline-start: calc(-1px - .04em);
margin-bottom: calc(-1px - .04em);
padding-bottom: calc(1px + .04em);
padding-inline-start: calc(1px + .04em);
position: relative;
z-index: 0;
}
.smyn-effect-neon::after {
content: attr(data-username-with-effects);
position: absolute;
content: attr(data-username-with-effects) / "";
filter: blur(4px);
inset: 0;
background: linear-gradient(to bottom left, var(--smyn-effect-light-2) 0, var(--smyn-effect-light-2) 6%, var(--smyn-effect-main-color) 20%, var(--smyn-effect-light-1) 50%, var(--smyn-effect-light-2) 56%, var(--smyn-effect-main-color) 70%, var(--smyn-effect-light-1) 100%);
background-clip: text;
-webkit-background-clip: text;
background-position: 100% 0;
background-size: 200% 200%;
-webkit-text-fill-color: transparent;
color: var(--smyn-effect-main-color);
filter: blur(calc(1px + .12em));
opacity: 0;
transition: opacity .15s ease-in-out;
-webkit-text-stroke-width: calc(1px + .04em);
-webkit-text-stroke-color: transparent;
perspective: 1px;
position: absolute;
text-indent: 0;
transition: opacity .1s ease-in-out;
-webkit-text-fill-color: transparent;
z-index: -1;
}
.smyn-effect-animated.smyn-effect-neon {
animation: smyn-neon-flicker 4s cubic-bezier(.24, .31, .36, .93) infinite;
animation-direction: normal;
animation-fill-mode: forwards;
.smyn-native-gradient-glow-active > [data-username-with-effects],
:where(span, div):has(> .smyn-native-gradient-glow-active),
:where(span, div):has(> * > .smyn-native-gradient-glow-active),
:where(span, div):has(> * > * > .smyn-native-gradient-glow-active) {
overflow: visible;
}
.smyn-effect-animated.smyn-effect-neon::after {
opacity: .8;
animation: smyn-neon-glow-flicker 1666ms linear infinite;
animation-direction: normal;
animation-fill-mode: forwards;
.smyn-native-gradient-glow-active > [data-username-with-effects]::after {
opacity: .7;
transition: opacity .1s ease-in-out;
}
.smyn-effect-toon {
--smyn-toon-stroke-width: calc(1.6px + 0.04em);
--smyn-toon-margin: calc(var(--smyn-toon-stroke-width)*-1);
paint-order: stroke fill;
position: relative;
-webkit-text-stroke-width: var(--smyn-toon-stroke-width);
-webkit-text-stroke-color: var(--smyn-effect-toon-stroke);
color: var(--smyn-effect-toon-stroke);
margin-inline: var(--smyn-toon-margin);
margin-bottom: var(--smyn-toon-margin);
padding-inline: var(--smyn-toon-stroke-width);
padding-bottom: var(--smyn-toon-stroke-width);
transition: color 266ms cubic-bezier(.43, .21, .27, .78);
.smyn-native-effect.smyn-native-message-effect {
display: inline;
}
.smyn-effect-toon::before {
background: linear-gradient(180deg, var(--white) 0, var(--smyn-effect-light-2) 8%, var(--smyn-effect-light-1) 15%, var(--smyn-effect-main-color) 25%, var(--smyn-effect-light-2) 45%, var(--smyn-effect-main-color) 55%, var(--white) 75%, var(--smyn-effect-light-2) 83%, var(--smyn-effect-light-1) 90%, var(--smyn-effect-main-color) 100%);
background-clip: text;
-webkit-background-clip: text;
background-size: 100% 400%;
content: attr(data-username-with-effects);
inset: 0;
padding-inline: var(--smyn-toon-stroke-width);
padding-bottom: var(--smyn-toon-margin);
position: absolute;
-webkit-text-fill-color: transparent;
-webkit-text-stroke-width: 0;
-webkit-text-stroke-color: transparent;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 266ms cubic-bezier(.43, .21, .27, .78);
.smyn-native-gummy-animated > [data-username-with-effects] {
--smyn-gummy-inline-headroom: .12em;
margin-inline: calc(var(--smyn-gummy-inline-headroom) * -1);
padding-inline: var(--smyn-gummy-inline-headroom);
text-overflow: clip;
}
.smyn-effect-pop {
--smyn-pop-stroke-width: calc(1.2px + 0.04em);
--smyn-pop-bottom-translate: 0.08em;
display: inline-block;
color: var(--white);
paint-order: stroke fill;
position: relative;
-webkit-text-stroke-color: var(--smyn-effect-dark-2);
-webkit-text-stroke-width: var(--smyn-pop-stroke-width);
margin-inline-start: calc(var(--smyn-pop-stroke-width)*-1);
margin-bottom: calc(var(--smyn-pop-stroke-width)*-1 - var(--smyn-pop-bottom-translate));
padding-bottom: calc(var(--smyn-pop-stroke-width) + var(--smyn-pop-bottom-translate));
padding-inline-start: var(--smyn-pop-stroke-width);
.smyn-container:hover {
text-decoration-color: var(--smyn-underline-color, currentcolor);
text-decoration-line: underline;
text-decoration-style: initial;
text-decoration-thickness: initial;
}
.smyn-effect-pop::before {
bottom: calc(var(--smyn-pop-bottom-translate)*-1 - var(--smyn-pop-stroke-width));
color: var(--smyn-effect-main-color);
content: attr(data-username-with-effects);
padding-inline-start: var(--smyn-pop-stroke-width);
position: absolute;
top: 0;
width: calc(100% - var(--smyn-pop-stroke-width));
z-index: -1;
-webkit-text-stroke-width: var(--smyn-pop-stroke-width);
-webkit-text-stroke-color: transparent;
background: linear-gradient(to bottom left, var(--smyn-effect-light-1) 0, var(--smyn-effect-light-1) 6%, var(--smyn-effect-main-color) 20%, var(--smyn-effect-main-color) 50%, var(--smyn-effect-light-1) 56%, var(--smyn-effect-main-color) 70%, var(--smyn-effect-main-color) 100%);
background-clip: text;
-webkit-background-clip: text;
background-position: 100% 0;
background-size: 200% 200%;
transform: translate3d(0, var(--smyn-pop-bottom-translate), 0);
-webkit-text-fill-color: transparent;
overflow: hidden;
.smyn-container:hover .smyn-native-effect > [data-username-with-effects] {
text-decoration-color: var(--smyn-underline-color, currentcolor);
text-decoration-line: underline;
text-decoration-style: initial;
text-decoration-thickness: initial;
}
.smyn-container:hover .smyn-native-pop > [data-username-with-effects],
.smyn-container:hover .smyn-native-gummy > [data-username-with-effects] {
text-decoration: none;
text-overflow: ellipsis;
}
.smyn-effect-pop:hover::before {
text-decoration: underline;
text-decoration-color: var(--smyn-effect-main-color);
text-underline-offset: calc(var(--smyn-pop-bottom-translate));
.smyn-container:hover .smyn-native-pop > [data-username-with-effects]::before {
text-decoration-color: var(--smyn-underline-color, currentcolor);
text-decoration-line: underline;
text-decoration-style: initial;
text-decoration-thickness: initial;
text-underline-offset: .08em;
}
.smyn-effect-animated.smyn-effect-toon::before {
animation: smyn-toon-animation 4s cubic-bezier(.44, .29, .48, 1) infinite;
animation-direction: normal;
animation-fill-mode: forwards;
.smyn-container:hover .smyn-native-gummy > [data-username-with-effects] {
position: relative;
}
.smyn-effect-animated.smyn-effect-pop {
animation: smyn-pop-main 4s cubic-bezier(.44, .29, .48, 1) infinite;
animation-direction: normal;
animation-fill-mode: forwards;
.smyn-container:hover .smyn-native-gummy > [data-username-with-effects]::before {
color: transparent;
content: attr(data-username-with-effects) / "";
inset: var(--custom-gummy-squish-headroom, 0) 0 0;
pointer-events: none;
position: absolute;
text-decoration-color: var(--smyn-underline-color, currentcolor);
text-decoration-line: underline;
text-decoration-style: initial;
text-decoration-thickness: initial;
white-space: var(--custom-display-name-styles-wrap);
}
.smyn-effect-animated.smyn-effect-pop::before {
animation: smyn-pop-shadow 4s cubic-bezier(.44, .29, .48, 1) infinite;
animation-direction: normal;
animation-fill-mode: forwards;
}
@keyframes smyn-neon-flicker {
0%,
15%,
18%,
20%,
23%,
25%,
50% {
color: var(--white);
}
16%,
22%,
28% {
color: var(--smyn-effect-neon-flicker);
}
51%,
100% {
color: var(--white);
}
}
@keyframes smyn-neon-glow-flicker {
0% {
background-position: 100% 0;
}
100% {
background-position: 0 100%;
}
}
@keyframes smyn-toon-animation {
0%,
5% {
background-position: 50% 0;
}
55%,
100% {
background-position: 50% 100%;
}
}
@keyframes smyn-pop-main {
0% {
transform: translateZ(0);
}
18% {
perspective: 1px;
transform: translate3d(0, -.05em, 0);
}
35% {
perspective: 1px;
transform: translate3d(0, .08em, 0);
}
50%,
100% {
perspective: 1px;
transform: translateZ(0);
}
}
@keyframes smyn-pop-shadow {
0% {
background-position: 100% 0;
perspective: 1px;
transform: translate3d(0, .08em, 0);
}
18% {
perspective: 1px;
transform: translate3d(0, .13em, 0);
}
35% {
perspective: 1px;
transform: translateZ(0);
}
50%,
100% {
background-position: 0 100%;
perspective: 1px;
transform: translate3d(0, .08em, 0);
}
}