🆙 Updated infostand & userpofile now mobile friendly and cards as background

This commit is contained in:
duckietm
2026-06-02 15:12:51 +02:00
parent 76ec66932b
commit c9ac6806dd
5 changed files with 177 additions and 34 deletions
@@ -1,6 +1,6 @@
import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer'; import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, SendMessageComposer, ToggleFavoriteGroup } from '../../api'; import { LocalizeText, SanitizeHtml, SendMessageComposer, ToggleFavoriteGroup } from '../../api';
import { Column, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../common'; import { Column, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../common';
import { useMessageEvent } from '../../hooks'; import { useMessageEvent } from '../../hooks';
import { GroupInformationView } from '../groups/views/GroupInformationView'; import { GroupInformationView } from '../groups/views/GroupInformationView';
@@ -68,9 +68,7 @@ export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
return ( return (
<div className="nitro-extended-profile-groups"> <div className="nitro-extended-profile-groups">
<div className="nitro-extended-profile-groups__sidebar"> <div className="nitro-extended-profile-groups__sidebar">
<div className="nitro-extended-profile-groups__count"> <div className="nitro-extended-profile-groups__count" dangerouslySetInnerHTML={ { __html: SanitizeHtml(LocalizeText('extendedprofile.groups.count', [ 'count' ], [ groups.length.toString() ])) } } />
{ LocalizeText('extendedprofile.groups.count', [ 'count' ], [ groups.length.toString() ]) }
</div>
<div className="nitro-extended-profile-groups__list"> <div className="nitro-extended-profile-groups__list">
{ groups.map((group, index) => { groups.map((group, index) =>
{ {
@@ -1,6 +1,6 @@
import { CreateLinkEvent, GetSessionDataManager, RelationshipStatusInfoMessageParser, RequestFriendComposer, UserProfileParser } from '@nitrots/nitro-renderer'; import { CreateLinkEvent, GetSessionDataManager, RelationshipStatusInfoMessageParser, RequestFriendComposer, UserProfileParser } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../api'; import { FriendlyTime, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../api';
import { LayoutAvatarImageView, LayoutBadgeImageView, Text, UserIdentityView } from '../../common'; import { LayoutAvatarImageView, LayoutBadgeImageView, Text, UserIdentityView } from '../../common';
import { badgeEmblemDefault } from '../../assets/images/leaderboard_badge'; import { badgeEmblemDefault } from '../../assets/images/leaderboard_badge';
import { level as profileLevelIcon, rooms as profileRoomsIcon } from '../../assets/images/user-profile'; import { level as profileLevelIcon, rooms as profileRoomsIcon } from '../../assets/images/user-profile';
@@ -61,10 +61,10 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
username={ userProfile.username } /> username={ userProfile.username } />
<p className="nitro-extended-profile__motto">{ userProfile.motto || '\u00A0' }</p> <p className="nitro-extended-profile__motto">{ userProfile.motto || '\u00A0' }</p>
<p className="nitro-extended-profile__meta"> <p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.created').replace(/%\w+%/g, '').trim() }</b> { userProfile.registration } <span dangerouslySetInnerHTML={ { __html: SanitizeHtml(LocalizeText('extendedprofile.created').replace(/%\w+%/g, '').trim()) } } /> { userProfile.registration }
</p> </p>
<p className="nitro-extended-profile__meta"> <p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.last.login').replace(/%\w+%/g, '').trim() }</b> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) } <span dangerouslySetInnerHTML={ { __html: SanitizeHtml(LocalizeText('extendedprofile.last.login').replace(/%\w+%/g, '').trim()) } } /> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) }
</p> </p>
<p className="nitro-extended-profile__meta nitro-extended-profile__meta--strong"> <p className="nitro-extended-profile__meta nitro-extended-profile__meta--strong">
<b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints } <b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints }
@@ -117,11 +117,11 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
<p <p
className="text-sm leading-none" className="text-sm leading-none"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: LocalizeText( __html: SanitizeHtml(LocalizeText(
'extendedprofile.friends.count', 'extendedprofile.friends.count',
['count'], ['count'],
[userProfile.friendsCount.toString()] [userProfile.friendsCount.toString()]
) ))
}} }}
/> />
<p className="nitro-extended-profile__relationships-label">{ LocalizeText('extendedprofile.relstatus') }</p> <p className="nitro-extended-profile__relationships-label">{ LocalizeText('extendedprofile.relstatus') }</p>
@@ -30,8 +30,6 @@ export const UserProfileView: FC<{}> = () =>
{ {
if(!userProfile) return; if(!userProfile) return;
// Open the navigator AND run the owner search (the composer alone never
// showed the navigator window, so the button looked dead).
CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`); CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`);
}; };
@@ -100,12 +98,15 @@ export const UserProfileView: FC<{}> = () =>
if(!userProfile) return null; if(!userProfile) return null;
const cardBackgroundId = userProfile.cardBackgroundId ?? 0;
const cardBackgroundClass = cardBackgroundId ? `profile-card-background card-background-${ cardBackgroundId }` : '';
return ( return (
<NitroCard className="nitro-extended-profile-window w-[600px] h-[600px] max-w-[96vw] max-h-[92vh]" uniqueKey="nitro-user-profile"> <NitroCard className="nitro-extended-profile-window w-[640px] h-[720px] max-w-[96vw] max-h-[92vh]" uniqueKey="nitro-user-profile">
<NitroCard.Header <NitroCard.Header
headerText={ LocalizeText('extendedprofile.caption') } headerText={ LocalizeText('extendedprofile.caption') }
onCloseClick={ onClose } /> onCloseClick={ onClose } />
<NitroCard.Content className="nitro-extended-profile-window__content overflow-hidden !p-0 flex flex-col"> <NitroCard.Content className={ `nitro-extended-profile-window__content overflow-hidden !p-0 flex flex-col ${ cardBackgroundClass }` }>
<div className="px-[10px] pt-[8px]"> <div className="px-[10px] pt-[8px]">
<UserContainerView <UserContainerView
userBadges={ userBadges } userBadges={ userBadges }
+58 -10
View File
@@ -1629,9 +1629,6 @@
.infostand-border.border-15 { border-color: #cbd5e1; } /* Silver */ .infostand-border.border-15 { border-color: #cbd5e1; } /* Silver */
.infostand-border.border-16 { border-color: #1f2937; } /* Black */ .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-17,
.infostand-border.border-18, .infostand-border.border-18,
.infostand-border.border-19, .infostand-border.border-19,
@@ -1662,8 +1659,6 @@
.infostand-border.border-24 { background-image: url('@/assets/images/backgrounds/borders/border_24.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'); } .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 { .profile-border {
width: 60px; width: 60px;
height: 76px; height: 76px;
@@ -1674,9 +1669,7 @@
background: rgba(255, 255, 255, 0.05); 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-0 { border: 2px dashed rgba(255, 255, 255, 0.25); }
.profile-border.border-1 { border-color: #ef4444; } .profile-border.border-1 { border-color: #ef4444; }
.profile-border.border-2 { border-color: #f97316; } .profile-border.border-2 { border-color: #f97316; }
.profile-border.border-3 { border-color: #eab308; } .profile-border.border-3 { border-color: #eab308; }
@@ -1693,8 +1686,6 @@
.profile-border.border-14 { border-color: #d4a020; } .profile-border.border-14 { border-color: #d4a020; }
.profile-border.border-15 { border-color: #cbd5e1; } .profile-border.border-15 { border-color: #cbd5e1; }
.profile-border.border-16 { border-color: #1f2937; } .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-17,
.profile-border.border-18, .profile-border.border-18,
.profile-border.border-19, .profile-border.border-19,
@@ -1719,4 +1710,61 @@
.profile-border.border-22 { background-image: url('@/assets/images/backgrounds/borders/border_22.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-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-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'); } .profile-border.border-25 { background-image: url('@/assets/images/backgrounds/borders/border_25.webp'); }
.card-background-2,
.card-background-3,
.card-background-4,
.card-background-5,
.card-background-6,
.card-background-7,
.card-background-12,
.card-background-13,
.card-background-28,
.card-background-30,
.card-background-35,
.card-background-52,
.card-background-55,
.card-background-56,
.card-background-58,
.card-background-60,
.card-background-95,
.card-background-116,
.card-background-122,
.card-background-127,
.card-background-129,
.card-background-131,
.card-background-144,
.card-background-149,
.card-background-150,
.card-background-161,
.card-background-185,
.card-background-187 {
--profile-card-text: #1a1a1a;
--profile-card-shadow: 0 1px 1px rgba(255, 255, 255, 0.65);
}
[class*="card-background-"] .nitro-extended-profile__username .username,
[class*="card-background-"] .nitro-extended-profile__motto,
[class*="card-background-"] .nitro-extended-profile__meta,
[class*="card-background-"] .nitro-extended-profile__meta b,
[class*="card-background-"] .nitro-extended-profile__meta span,
[class*="card-background-"] .nitro-extended-profile__status-text,
[class*="card-background-"] .nitro-extended-profile__relationships-label,
[class*="card-background-"] .nitro-extended-profile__relationship-subcopy,
[class*="card-background-"] .nitro-extended-profile__link,
[class*="card-background-"] .nitro-extended-profile__right > p,
[class*="card-background-"] .nitro-extended-profile__right > p b {
color: var(--profile-card-text, #fff) !important;
text-shadow: var(--profile-card-shadow, 0 1px 2px rgba(0, 0, 0, 0.55));
}
.profile-card-background[class*="card-background-"]:not(.nitro-extended-profile-window__content) {
color: var(--profile-card-text, #fff);
text-shadow: var(--profile-card-shadow, 0 1px 2px rgba(0, 0, 0, 0.55));
}
.profile-card-background[class*="card-background-"]:not(.nitro-extended-profile-window__content) .text-white {
color: var(--profile-card-text, #fff) !important;
}
+107 -11
View File
@@ -250,6 +250,7 @@
transform: translateY(-50%); transform: translateY(-50%);
overflow: hidden; overflow: hidden;
} }
.nitro-extended-profile__relationship-head .avatar-image { .nitro-extended-profile__relationship-head .avatar-image {
position: absolute !important; position: absolute !important;
inset: 0 !important; inset: 0 !important;
@@ -330,6 +331,10 @@
background: #ece8dc; background: #ece8dc;
} }
.nitro-extended-profile-window__content.profile-card-background .nitro-extended-profile-window__body {
background: transparent;
}
.nitro-extended-profile-window__body--groups { .nitro-extended-profile-window__body--groups {
min-height: 249px; min-height: 249px;
} }
@@ -377,10 +382,8 @@
.nitro-extended-profile-groups__details { .nitro-extended-profile-groups__details {
min-width: 0; min-width: 0;
min-height: 236px; min-height: 236px;
border: 1px solid #9e9e9e; max-height: 100%;
border-radius: 14px; overflow-y: auto;
background: #bdbbbb;
padding: 11px;
} }
.nitro-extended-profile-group-info { .nitro-extended-profile-group-info {
@@ -389,7 +392,7 @@
gap: 12px; gap: 12px;
min-height: 100%; min-height: 100%;
border: 1px solid #9f9f9f; border: 1px solid #9f9f9f;
border-radius: 10px; border-radius: 12px;
background: #efede4; background: #efede4;
padding: 12px 14px; padding: 12px 14px;
} }
@@ -407,6 +410,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
} }
.nitro-extended-profile-group-info__meta { .nitro-extended-profile-group-info__meta {
@@ -457,9 +461,14 @@
margin-top: auto; margin-top: auto;
} }
.nitro-extended-profile-group-info .group-badge .badge-image { .nitro-extended-profile-group-info__badge-wrap .group-badge {
transform: scale(2.1); width: 100% !important;
transform-origin: center; height: 100% !important;
transform: none !important;
background-size: contain !important;
background-position: center !important;
background-repeat: no-repeat !important;
image-rendering: pixelated;
} }
.nitro-extended-profile-window .layout-grid-item.active { .nitro-extended-profile-window .layout-grid-item.active {
@@ -479,7 +488,94 @@
justify-content: center; justify-content: center;
} }
.nitro-extended-profile-window .layout-grid-item .badge-image.group-badge { .nitro-extended-profile-groups__item {
width: auto !important; overflow: hidden;
height: auto !important; }
.nitro-extended-profile-groups__item .group-badge {
width: 40px !important;
height: 40px !important;
transform: none !important;
background-size: contain !important;
background-position: center !important;
background-repeat: no-repeat !important;
image-rendering: pixelated;
}
@media (max-width: 575.98px) {
.nitro-extended-profile-window__content {
overflow-y: auto;
}
.nitro-extended-profile-window__body--groups {
flex: 0 0 auto;
overflow: visible;
}
.nitro-extended-profile-window__panel {
height: auto;
}
.nitro-extended-profile__top {
grid-template-columns: minmax(0, 1fr);
min-height: 0;
}
.nitro-extended-profile__separator {
display: none;
}
.nitro-extended-profile-groups {
display: flex;
flex-direction: column;
gap: 8px;
height: auto;
min-height: 0;
}
.nitro-extended-profile-groups__sidebar {
min-width: 0;
flex: 0 0 auto;
}
.nitro-extended-profile-groups__list {
display: flex;
flex: 0 0 auto;
flex-direction: row;
flex-wrap: nowrap;
gap: 6px;
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding-right: 0;
padding-bottom: 6px;
scrollbar-width: thin;
}
.nitro-extended-profile-groups__item {
flex: 0 0 auto;
margin-bottom: 0;
}
.nitro-extended-profile-groups__details {
min-height: 0;
max-height: none;
overflow: visible;
}
.nitro-extended-profile-group-info {
grid-template-columns: 72px minmax(0, 1fr);
gap: 10px;
padding: 10px;
}
.nitro-extended-profile-group-info__badge-wrap {
width: 64px;
height: 64px;
}
.nitro-extended-profile-group-info__button {
min-width: 0;
width: 100%;
}
} }