diff --git a/src/api/room/widgets/AvatarInfoUser.ts b/src/api/room/widgets/AvatarInfoUser.ts index a5b239b..4c75e00 100644 --- a/src/api/room/widgets/AvatarInfoUser.ts +++ b/src/api/room/widgets/AvatarInfoUser.ts @@ -24,6 +24,7 @@ export class AvatarInfoUser implements IAvatarInfo public standId: number = 0; public overlayId: number = 0; public cardBackgroundId: number = 0; + public borderId: number = 0; public webID: number = 0; public xp: number = 0; public userType: number = -1; diff --git a/src/api/room/widgets/AvatarInfoUtilities.ts b/src/api/room/widgets/AvatarInfoUtilities.ts index f786aff..1cc0620 100644 --- a/src/api/room/widgets/AvatarInfoUtilities.ts +++ b/src/api/room/widgets/AvatarInfoUtilities.ts @@ -194,6 +194,7 @@ export class AvatarInfoUtilities userInfo.standId = userData.stand; userInfo.overlayId = userData.overlay; userInfo.cardBackgroundId = userData.cardBackground ?? 0; + userInfo.borderId = (userData as any).borderId ?? 0; userInfo.achievementScore = userData.activityPoints; userInfo.webID = userData.webID; userInfo.roomIndex = userData.roomIndex; diff --git a/src/assets/images/backgrounds/borders/border_17.webp b/src/assets/images/backgrounds/borders/border_17.webp new file mode 100644 index 0000000..78f5a10 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_17.webp differ diff --git a/src/assets/images/backgrounds/borders/border_18.webp b/src/assets/images/backgrounds/borders/border_18.webp new file mode 100644 index 0000000..caec392 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_18.webp differ diff --git a/src/assets/images/backgrounds/borders/border_19.webp b/src/assets/images/backgrounds/borders/border_19.webp new file mode 100644 index 0000000..4092794 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_19.webp differ diff --git a/src/assets/images/backgrounds/borders/border_20.webp b/src/assets/images/backgrounds/borders/border_20.webp new file mode 100644 index 0000000..8227088 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_20.webp differ diff --git a/src/assets/images/backgrounds/borders/border_21.webp b/src/assets/images/backgrounds/borders/border_21.webp new file mode 100644 index 0000000..3b2fe8c Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_21.webp differ diff --git a/src/assets/images/backgrounds/borders/border_22.webp b/src/assets/images/backgrounds/borders/border_22.webp new file mode 100644 index 0000000..d09ce2b Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_22.webp differ diff --git a/src/assets/images/backgrounds/borders/border_23.webp b/src/assets/images/backgrounds/borders/border_23.webp new file mode 100644 index 0000000..40ea0a5 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_23.webp differ diff --git a/src/assets/images/backgrounds/borders/border_24.webp b/src/assets/images/backgrounds/borders/border_24.webp new file mode 100644 index 0000000..6da2b04 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_24.webp differ diff --git a/src/assets/images/backgrounds/borders/border_25.webp b/src/assets/images/backgrounds/borders/border_25.webp new file mode 100644 index 0000000..0826e68 Binary files /dev/null and b/src/assets/images/backgrounds/borders/border_25.webp differ diff --git a/src/components/backgrounds/BackgroundsView.tsx b/src/components/backgrounds/BackgroundsView.tsx index 2aa2196..1c51398 100644 --- a/src/components/backgrounds/BackgroundsView.tsx +++ b/src/components/backgrounds/BackgroundsView.tsx @@ -18,12 +18,14 @@ interface BackgroundsViewProps { setSelectedOverlay: Dispatch>; selectedCardBackground: number; setSelectedCardBackground: Dispatch>; + selectedBorder: number; + setSelectedBorder: Dispatch>; } -const TABS = ['backgrounds', 'stands', 'overlays', 'cards'] as const; +const TABS = ['backgrounds', 'stands', 'overlays', 'cards', 'borders'] as const; type TabType = typeof TABS[number]; -type RemoteData = Partial>; +type RemoteData = Partial>; let backgroundsDataPromise: Promise | null = null; @@ -48,7 +50,9 @@ export const BackgroundsView: FC = ({ selectedOverlay, setSelectedOverlay, selectedCardBackground, - setSelectedCardBackground + setSelectedCardBackground, + selectedBorder, + setSelectedBorder }) => { const [activeTab, setActiveTab] = useState('backgrounds'); @@ -62,7 +66,7 @@ export const BackgroundsView: FC = ({ return configData.map(item => ({ id: typeof item === 'number' ? item : item[idField] })); }, []); - const readData = useCallback((key: 'backgrounds.data' | 'stands.data' | 'overlays.data' | 'cards.data'): any[] => + const readData = useCallback((key: 'backgrounds.data' | 'stands.data' | 'overlays.data' | 'cards.data' | 'borders.data'): any[] => { const fromRemote = remoteData?.[key]; if(Array.isArray(fromRemote)) return fromRemote; @@ -73,21 +77,28 @@ export const BackgroundsView: FC = ({ backgrounds: processData(readData('backgrounds.data'), 'backgroundId'), stands: processData(readData('stands.data'), 'standId'), overlays: processData(readData('overlays.data'), 'overlayId'), - cards: processData(readData('cards.data').length ? readData('cards.data') : readData('backgrounds.data'), 'backgroundId') + cards: processData(readData('cards.data').length ? readData('cards.data') : readData('backgrounds.data'), 'backgroundId'), + borders: processData(readData('borders.data'), 'borderId') }), [processData, readData]); const handleSelection = useCallback((id: number) => { if (!roomSession) return; - const setters = { backgrounds: setSelectedBackground, stands: setSelectedStand, overlays: setSelectedOverlay, cards: setSelectedCardBackground }; + const setters = { backgrounds: setSelectedBackground, stands: setSelectedStand, overlays: setSelectedOverlay, cards: setSelectedCardBackground, borders: setSelectedBorder }; - const currentValues = { backgrounds: selectedBackground, stands: selectedStand, overlays: selectedOverlay, cards: selectedCardBackground }; + const currentValues = { backgrounds: selectedBackground, stands: selectedStand, overlays: selectedOverlay, cards: selectedCardBackground, borders: selectedBorder }; setters[activeTab](id); const newValues = { ...currentValues, [activeTab]: id }; - roomSession.sendBackgroundMessage( newValues.backgrounds, newValues.stands, newValues.overlays, newValues.cards ); - }, [activeTab, roomSession, selectedBackground, selectedStand, selectedOverlay, selectedCardBackground, setSelectedBackground, setSelectedStand, setSelectedOverlay, setSelectedCardBackground]); + roomSession.sendBackgroundMessage( newValues.backgrounds, newValues.stands, newValues.overlays, newValues.cards, newValues.borders ); + }, [activeTab, roomSession, selectedBackground, selectedStand, selectedOverlay, selectedCardBackground, selectedBorder, setSelectedBackground, setSelectedStand, setSelectedOverlay, setSelectedCardBackground, setSelectedBorder]); + + const itemTypeFor = (tab: TabType): string => { + if(tab === 'cards') return 'card-background'; + if(tab === 'borders') return 'border'; + return tab.slice(0, -1); + }; const renderItem = useCallback((item: ItemData, type: string) => ( = ({ > ), [handleSelection]); @@ -120,7 +135,7 @@ export const BackgroundsView: FC = ({ Select an Option - {allData[activeTab].map(item => renderItem(item, activeTab === 'cards' ? 'card-background' : activeTab.slice(0, -1)))} + {allData[activeTab].map(item => renderItem(item, itemTypeFor(activeTab)))} diff --git a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx index 8bbfd3f..cbfb72d 100644 --- a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx +++ b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.tsx @@ -229,7 +229,7 @@ export const AvatarInfoWidgetView: FC<{}> = props => } } - return setAvatarInfo(null) } />; + return setAvatarInfo(null) } />; case AvatarInfoUser.BOT: return setAvatarInfo(null) } />; case AvatarInfoRentableBot.RENTABLE_BOT: diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx index d5c31d3..8a84dc0 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; -import { FC, FocusEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react'; +import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; import { FaPencilAlt, FaTimes } from 'react-icons/fa'; -import { AvatarInfoUser, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api'; +import { AvatarInfoUser, CloneObject, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api'; import { Base, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserIdentityView, UserProfileIconView } from '../../../../../common'; -import { useMessageEvent, useRoom } from '../../../../../hooks'; +import { useMessageEvent, useNitroEvent, useRoom } from '../../../../../hooks'; import { InfoStandBadgeSlotView } from './InfoStandBadgeSlotView'; import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView'; import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView'; @@ -12,244 +12,299 @@ import { BackgroundsView } from '../../../../backgrounds/BackgroundsView'; interface InfoStandWidgetUserViewProps { avatarInfo: AvatarInfoUser; + setAvatarInfo: Dispatch>; onClose: () => void; } -export const InfoStandWidgetUserView: FC = props => -{ - const { avatarInfo = null, onClose = null } = props; - const [motto, setMotto] = useState(null); - const [isEditingMotto, setIsEditingMotto] = useState(false); - const [relationships, setRelationships] = useState(null); - const [backgroundId, setBackgroundId] = useState(null); - const [standId, setStandId] = useState(null); - const [overlayId, setOverlayId] = useState(null); - const [cardBackgroundId, setCardBackgroundId] = useState(null); - const [isVisible, setIsVisible] = useState(false); - const { roomSession = null } = useRoom(); +export const InfoStandWidgetUserView: FC = props => { + const { avatarInfo = null, setAvatarInfo = null, onClose = null } = props; + const [motto, setMotto] = useState(null); + const [isEditingMotto, setIsEditingMotto] = useState(false); + const [relationships, setRelationships] = useState(null); + const [backgroundId, setBackgroundId] = useState(null); + const [standId, setStandId] = useState(null); + const [overlayId, setOverlayId] = useState(null); + const [cardBackgroundId, setCardBackgroundId] = useState(null); + const [borderId, setBorderId] = useState(null); + const [isVisible, setIsVisible] = useState(false); + const { roomSession = null } = useRoom(); - const infostandBackgroundClass = `background-${backgroundId ?? 'default'}`; - const infostandStandClass = `stand-${standId ?? 'default'}`; - const infostandOverlayClass = `overlay-${overlayId ?? 'default'}`; - const infostandCardBackgroundClass = cardBackgroundId ? `card-background-${cardBackgroundId}` : ''; - const handleProfileClick = useCallback(() => - { - GetUserProfile(avatarInfo.webID); - }, [avatarInfo.webID]); + const infostandBackgroundClass = `background-${backgroundId ?? 'default'}`; + const infostandStandClass = `stand-${standId ?? 'default'}`; + const infostandOverlayClass = `overlay-${overlayId ?? 'default'}`; + const infostandCardBackgroundClass = cardBackgroundId ? `card-background-${cardBackgroundId}` : ''; + const infostandBorderClass = borderId ? `border-${borderId}` : ''; + const handleProfileClick = useCallback(() => { GetUserProfile(avatarInfo.webID); }, [avatarInfo.webID]); - const handleEditClick = useCallback((event: React.MouseEvent) => - { - event.stopPropagation(); setIsVisible(prev => !prev); - }, []); + const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setIsVisible(prev => !prev); }, []); - const saveMotto = (motto: string) => - { - if (!isEditingMotto || motto.length > GetConfigurationValue('motto.max.length', 38) || !roomSession) return; + const saveMotto = (motto: string) => { + if (!isEditingMotto || motto.length > GetConfigurationValue('motto.max.length', 38) || !roomSession) return; - roomSession.sendMottoMessage(motto); - setIsEditingMotto(false); - }; + roomSession.sendMottoMessage(motto); + setIsEditingMotto(false); + }; - const onMottoBlur = (event: FocusEvent) => saveMotto(event.target.value); + const onMottoBlur = (event: FocusEvent) => saveMotto(event.target.value); - const onMottoKeyDown = (event: KeyboardEvent) => - { - event.stopPropagation(); + const onMottoKeyDown = (event: KeyboardEvent) => { + event.stopPropagation(); - switch (event.key) - { - case 'Enter': - saveMotto((event.target as HTMLInputElement).value); - return; - } - }; + switch (event.key) { + case 'Enter': + saveMotto((event.target as HTMLInputElement).value); + return; + } + }; - useMessageEvent(RelationshipStatusInfoEvent, event => - { - const parser = event.getParser(); + useNitroEvent(RoomSessionUserBadgesEvent.RSUBE_BADGES, event => { + if (!avatarInfo || avatarInfo.webID !== event.userId) return; - if (!avatarInfo || avatarInfo.webID !== parser.userId) return; - - setRelationships(parser); + // Deduplicate badges from server + const seen = new Set(); + const dedupedBadges = event.badges.map(code => { + if (!code || seen.has(code)) return ''; + seen.add(code); + return code; }); - useEffect(() => - { - setIsEditingMotto(false); - setMotto(avatarInfo.motto); - setBackgroundId(avatarInfo.backgroundId); - setStandId(avatarInfo.standId); - setOverlayId(avatarInfo.overlayId); - setCardBackgroundId(avatarInfo.cardBackgroundId ?? 0); + const oldBadges = avatarInfo.badges.join(''); - SendMessageComposer(new UserRelationshipsComposer(avatarInfo.webID)); + if (oldBadges === dedupedBadges.join('')) return; - return () => - { - setRelationships(null); - }; - }, [avatarInfo]); + setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; - if (!avatarInfo) return null; + const newValue = CloneObject(prevValue); + newValue.badges = dedupedBadges; + return newValue; + }); + }); - return ( - <> - - -
-
-
- - -
- -
-
-
-
-
- - - - - - {avatarInfo.type === AvatarInfoUser.OWN_USER && ( - - )} - - { (() => - { - const maxSlots = GetConfigurationValue('user.badges.max.slots', 5); - const isOwnUser = avatarInfo.type === AvatarInfoUser.OWN_USER; - const showGroup = maxSlots <= 5; + useNitroEvent(RoomSessionUserFigureUpdateEvent.USER_FIGURE, event => { + if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return; - const items: React.ReactNode[] = []; - items.push(); + setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; - if(showGroup) - { - items.push( - 0} onClick={event => GetGroupInformation(avatarInfo.groupId)}> - {avatarInfo.groupId > 0 && } - - ); - } - else - { - items.push(); - } + const newValue = CloneObject(prevValue); + newValue.figure = event.figure; + newValue.motto = event.customInfo; + newValue.achievementScore = event.activityPoints; + newValue.nickIcon = event.nickIcon; + newValue.prefixText = event.prefixText; + newValue.prefixColor = event.prefixColor; + newValue.prefixIcon = event.prefixIcon; + newValue.prefixEffect = event.prefixEffect; + newValue.displayOrder = event.displayOrder; + newValue.backgroundId = event.backgroundId; + newValue.standId = event.standId; + newValue.overlayId = event.overlayId; + newValue.cardBackgroundId = event.cardBackgroundId ?? 0; + newValue.borderId = event.borderId ?? 0; + return newValue; + }); + }); - const startIdx = showGroup ? 1 : 2; - for(let i = startIdx; i < maxSlots; i++) - { - items.push(); - } + useNitroEvent(RoomSessionFavoriteGroupUpdateEvent.FAVOURITE_GROUP_UPDATE, event => { + if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return; - const rows: React.ReactNode[][] = []; - for(let i = 0; i < items.length; i += 2) - { - rows.push(items.slice(i, i + 2)); - } + setAvatarInfo(prevValue => { + if (!prevValue) return prevValue; - return rows.map((row, idx) => ( - {row} - )); - })() } - -
-
-
-
- - {avatarInfo.type !== AvatarInfoUser.OWN_USER && ( - - {motto} - - )} - {avatarInfo.type === AvatarInfoUser.OWN_USER && ( - - - - {!isEditingMotto && ( - setIsEditingMotto(true)}> - {motto} - - )} - {isEditingMotto && ( - ('motto.max.length', 38)} - type="text" - value={motto} - onBlur={onMottoBlur} - onChange={event => setMotto(event.target.value)} - onKeyDown={onMottoKeyDown} - /> - )} - - - )} - -
-
-
- - {LocalizeText('infostand.text.achievement_score') + ' ' + avatarInfo.achievementScore} - - {avatarInfo.carryItem > 0 && ( - <> -
- - {LocalizeText('infostand.text.handitem', ['item'], [LocalizeText('handitem' + avatarInfo.carryItem)])} - - - )} -
-
- -
- {GetConfigurationValue('user.tags.enabled') && ( - - - + const newValue = CloneObject(prevValue); + const clearGroup = (event.status === -1) || (event.habboGroupId <= 0); + + newValue.groupId = clearGroup ? -1 : event.habboGroupId; + newValue.groupName = clearGroup ? null : event.habboGroupName; + newValue.groupBadgeId = clearGroup ? null : GetSessionDataManager().getGroupBadge(event.habboGroupId); + return newValue; + }); + }); + + useMessageEvent(RelationshipStatusInfoEvent, event => { + const parser = event.getParser(); + + if (!avatarInfo || avatarInfo.webID !== parser.userId) return; + + setRelationships(parser); + }); + + useEffect(() => { + setIsEditingMotto(false); + setMotto(avatarInfo.motto); + setBackgroundId(avatarInfo.backgroundId); + setStandId(avatarInfo.standId); + setOverlayId(avatarInfo.overlayId); + setCardBackgroundId(avatarInfo.cardBackgroundId ?? 0); + setBorderId(avatarInfo.borderId ?? 0); + + SendMessageComposer(new UserRelationshipsComposer(avatarInfo.webID)); + + return () => { + setRelationships(null); + }; + }, [avatarInfo]); + + if (!avatarInfo) return null; + + return ( + <> +
+ {borderId ? : null} + + +
+
+
+ + +
+ +
+
+
+
+
+ + + + + + {avatarInfo.type === AvatarInfoUser.OWN_USER && ( + + )} + + { (() => { + const maxSlots = GetConfigurationValue('user.badges.max.slots', 5); + const isOwnUser = avatarInfo.type === AvatarInfoUser.OWN_USER; + const showGroup = maxSlots <= 5; + + const items: React.ReactNode[] = []; + items.push(); + + if(showGroup) { + items.push( + 0} onClick={event => GetGroupInformation(avatarInfo.groupId)}> + {avatarInfo.groupId > 0 && } + + ); + } else { + items.push(); + } + + const startIdx = showGroup ? 1 : 2; + for(let i = startIdx; i < maxSlots; i++) { + items.push(); + } + + const rows: React.ReactNode[][] = []; + for(let i = 0; i < items.length; i += 2) { + rows.push(items.slice(i, i + 2)); + } + + return rows.map((row, idx) => ( + {row} + )); + })() } + +
+
+
+
+ + {avatarInfo.type !== AvatarInfoUser.OWN_USER && ( + + {motto} + + )} + {avatarInfo.type === AvatarInfoUser.OWN_USER && ( + + + + {!isEditingMotto && ( + setIsEditingMotto(true)}> + {motto}  + )} - - - {isVisible && avatarInfo.type === AvatarInfoUser.OWN_USER && ( -
- -
+ {isEditingMotto && ( + ('motto.max.length', 38)} + type="text" + value={motto} + onBlur={onMottoBlur} + onChange={event => setMotto(event.target.value)} + onKeyDown={onMottoKeyDown} + /> + )} +
+
+ )} +
+
+
+
+ + {LocalizeText('infostand.text.achievement_score') + ' ' + avatarInfo.achievementScore} + + {avatarInfo.carryItem > 0 && ( + <> +
+ + {LocalizeText('infostand.text.handitem', ['item'], [LocalizeText('handitem' + avatarInfo.carryItem)])} + + )} - - ); +
+
+ +
+ {GetConfigurationValue('user.tags.enabled') && ( + + + + )} +
+
+
+ {isVisible && avatarInfo.type === AvatarInfoUser.OWN_USER && ( +
+ +
+ )} + + ); }; diff --git a/src/css/backgrounds/BackgroundsView.css b/src/css/backgrounds/BackgroundsView.css index 1aa6114..a5e1fa8 100644 --- a/src/css/backgrounds/BackgroundsView.css +++ b/src/css/backgrounds/BackgroundsView.css @@ -1594,4 +1594,129 @@ &.overlay-8 { background-image: url('@/assets/images/backgrounds/overlay/overlay_8.png'); } -} \ No newline at end of file +} + +/* Infostand-only colored border. A thin CSS-drawn rounded ring drawn 5px + outside the card on each side. Pure CSS — no artwork files. */ +.infostand-border { + position: absolute; + top: -5px; + bottom: -5px; + left: -5px; + right: -5px; + pointer-events: none; + z-index: 5; + border-width: 5px; + border-style: solid; + border-radius: 10px; + box-sizing: content-box; +} + +.infostand-border.border-1 { border-color: #ef4444; } /* Red */ +.infostand-border.border-2 { border-color: #f97316; } /* Orange */ +.infostand-border.border-3 { border-color: #eab308; } /* Yellow */ +.infostand-border.border-4 { border-color: #84cc16; } /* Lime */ +.infostand-border.border-5 { border-color: #22c55e; } /* Green */ +.infostand-border.border-6 { border-color: #14b8a6; } /* Teal */ +.infostand-border.border-7 { border-color: #06b6d4; } /* Cyan */ +.infostand-border.border-8 { border-color: #3b82f6; } /* Blue */ +.infostand-border.border-9 { border-color: #6366f1; } /* Indigo */ +.infostand-border.border-10 { border-color: #a855f7; } /* Purple */ +.infostand-border.border-11 { border-color: #ec4899; } /* Pink */ +.infostand-border.border-12 { border-color: #f43f5e; } /* Rose */ +.infostand-border.border-13 { border-color: #92400e; } /* Brown */ +.infostand-border.border-14 { border-color: #d4a020; } /* Gold */ +.infostand-border.border-15 { border-color: #cbd5e1; } /* Silver */ +.infostand-border.border-16 { border-color: #1f2937; } /* Black */ + +/* Image-based borders (17-25). These override the colour-border insets and + strip the CSS border so the artwork sits ~22px outside the card and + stretches to fill the frame area. */ +.infostand-border.border-17, +.infostand-border.border-18, +.infostand-border.border-19, +.infostand-border.border-20, +.infostand-border.border-21, +.infostand-border.border-22, +.infostand-border.border-23, +.infostand-border.border-24, +.infostand-border.border-25 { + top: -22px; + bottom: -22px; + left: -22px; + right: -22px; + border: none; + border-radius: 0; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; +} + +.infostand-border.border-17 { background-image: url('@/assets/images/backgrounds/borders/border_17.webp'); } +.infostand-border.border-18 { background-image: url('@/assets/images/backgrounds/borders/border_18.webp'); } +.infostand-border.border-19 { background-image: url('@/assets/images/backgrounds/borders/border_19.webp'); } +.infostand-border.border-20 { background-image: url('@/assets/images/backgrounds/borders/border_20.webp'); } +.infostand-border.border-21 { background-image: url('@/assets/images/backgrounds/borders/border_21.webp'); } +.infostand-border.border-22 { background-image: url('@/assets/images/backgrounds/borders/border_22.webp'); } +.infostand-border.border-23 { background-image: url('@/assets/images/backgrounds/borders/border_23.webp'); } +.infostand-border.border-24 { background-image: url('@/assets/images/backgrounds/borders/border_24.webp'); } +.infostand-border.border-25 { background-image: url('@/assets/images/backgrounds/borders/border_25.webp'); } + +/* Picker thumbnails inside the BackgroundsView "Borders" tab. + Each thumbnail is a small rounded box outlined in its border colour. */ +.profile-border { + width: 60px; + height: 76px; + border-width: 4px; + border-style: solid; + border-radius: 8px; + box-sizing: border-box; + background: rgba(255, 255, 255, 0.05); +} + +/* border-0 = no border (default) — show as a dashed translucent outline */ +.profile-border.border-0 { border: 2px dashed rgba(255, 255, 255, 0.25); } + +.profile-border.border-1 { border-color: #ef4444; } +.profile-border.border-2 { border-color: #f97316; } +.profile-border.border-3 { border-color: #eab308; } +.profile-border.border-4 { border-color: #84cc16; } +.profile-border.border-5 { border-color: #22c55e; } +.profile-border.border-6 { border-color: #14b8a6; } +.profile-border.border-7 { border-color: #06b6d4; } +.profile-border.border-8 { border-color: #3b82f6; } +.profile-border.border-9 { border-color: #6366f1; } +.profile-border.border-10 { border-color: #a855f7; } +.profile-border.border-11 { border-color: #ec4899; } +.profile-border.border-12 { border-color: #f43f5e; } +.profile-border.border-13 { border-color: #92400e; } +.profile-border.border-14 { border-color: #d4a020; } +.profile-border.border-15 { border-color: #cbd5e1; } +.profile-border.border-16 { border-color: #1f2937; } + +/* Image-border picker thumbnails — drop the CSS frame and show the artwork. */ +.profile-border.border-17, +.profile-border.border-18, +.profile-border.border-19, +.profile-border.border-20, +.profile-border.border-21, +.profile-border.border-22, +.profile-border.border-23, +.profile-border.border-24, +.profile-border.border-25 { + border: none; + background: transparent; + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +.profile-border.border-17 { background-image: url('@/assets/images/backgrounds/borders/border_17.webp'); } +.profile-border.border-18 { background-image: url('@/assets/images/backgrounds/borders/border_18.webp'); } +.profile-border.border-19 { background-image: url('@/assets/images/backgrounds/borders/border_19.webp'); } +.profile-border.border-20 { background-image: url('@/assets/images/backgrounds/borders/border_20.webp'); } +.profile-border.border-21 { background-image: url('@/assets/images/backgrounds/borders/border_21.webp'); } +.profile-border.border-22 { background-image: url('@/assets/images/backgrounds/borders/border_22.webp'); } +.profile-border.border-23 { background-image: url('@/assets/images/backgrounds/borders/border_23.webp'); } +.profile-border.border-24 { background-image: url('@/assets/images/backgrounds/borders/border_24.webp'); } +.profile-border.border-25 { background-image: url('@/assets/images/backgrounds/borders/border_25.webp'); } \ No newline at end of file