From ca0bd2c12934a9c6c284bb4ea368a76a3cb2f1aa Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Sat, 15 Aug 2026 07:23:10 -0700 Subject: [PATCH] SMYN: New styles + broader capability. (#1298) * New Styles. * More locations, DM non-hover styling, etc. * Corrections. * More Options. * Fixes. * Fixes. --- src/plugins/showMeYourName/index.tsx | 565 +++++++++++++++++++-------- src/plugins/showMeYourName/style.css | 295 +++----------- 2 files changed, 454 insertions(+), 406 deletions(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 465e14895..a12d9a21a 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -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( + "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 | 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 + ? + : 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 { - 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 ( + + ); +} + +function needsGradientAnimationOverride(styles: DisplayNameStyles | null | undefined): boolean { + return styles?.effectId === DisplayNameEffects.GRADIENT + || styles?.effectId === DisplayNameEffects.GLOW; +} + +function getSecondaryNameStyle( + style: Record | null, + useNativeEffect: boolean, + ignoreEffects: boolean, + animateGradient: boolean, + showGradientGlow: boolean, +): Record { + 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 = ( {mentionSymbol && {mentionSymbol}} {( - {first.wrapped} + {shouldShowDisplayNameEffect && authorDisplayNameStyles + ? + : first.wrapped} + )} {[ @@ -726,26 +852,30 @@ function renderUsername( - {name.wrapped} + {shouldShowSecondaryDisplayNameEffect && authorDisplayNameStyles + ? + : name.wrapped} + {name.suffix} @@ -758,28 +888,25 @@ function renderUsername( const hoveringMessageMap = new Map(); const hoveringRepliesMap = new Map(); -const hoveringReactionPopoutSet = new Set(); -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 }); diff --git a/src/plugins/showMeYourName/style.css b/src/plugins/showMeYourName/style.css index 6c16219b5..02dc4fe66 100644 --- a/src/plugins/showMeYourName/style.css +++ b/src/plugins/showMeYourName/style.css @@ -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); - } -} \ No newline at end of file