fix(client): catalogo mobile, BC/navigator/profilo/amici

- Catalogo Hippiehotel responsive su mobile (finestra 96vw/72vh, rail
  tappabile, sotto-pannelli ristretti); duckie ripristinato come entita
  separata (revert modifiche scrollbar sul suo CSS)
- BC e catalogo normale seguono entrambi il toggle tema; il duckie non
  duplica piu il logo nelle pagine info_duckets
- Navigator: highlight della tab segue currentTabCode (era bloccato su
  Pubblici mentre il contenuto cambiava)
- Inventario: link inventory/show/<tab> per deep-link a una scheda
- User Profile: avatar visibile e allineato a bg/stand, finestra piu
  grande, bordi puliti, Created/Last login mostrano il valore, bottoni
  Change Looks/Rooms/Change Badges/Add friends/Achievement funzionanti
- Amici: header non piu sovradimensionati e teste avatar inquadrate
  (regole flat: quelle annidate .nitro-friends{&...} non si applicavano)
This commit is contained in:
medievalshell
2026-05-30 05:49:04 +02:00
parent d79bdd33e1
commit f7e78674c6
11 changed files with 125 additions and 65 deletions
@@ -252,7 +252,8 @@ const CatalogClassicViewInner: FC<{}> = () =>
<div className="nitro-catalog-classic-layout-header-shell">
<CatalogBreadcrumbView />
<div className="nitro-catalog-classic-layout-hero">
{ !!currentPage?.localization?.getImage(0) && <img src={ currentPage.localization.getImage(0) } /> }
{ /* info_duckets renders its own logo in the body (BcInfoView) — don't duplicate it in the hero */ }
{ (currentPage?.layoutCode !== 'info_duckets') && !!currentPage?.localization?.getImage(0) && <img src={ currentPage.localization.getImage(0) } /> }
</div>
</div>
<div className="nitro-catalog-classic-layout-container">
+8 -4
View File
@@ -37,6 +37,10 @@ const CatalogModernViewInner: FC<{}> = () =>
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
: undefined;
// Desktop = fixed 780x520. On mobile the window clamps below the viewport so
// it reads as a dialog (with margins) instead of filling the whole phone
// screen — applies to both the normal catalog and the Builders Club.
const catalogCardSize = 'w-[780px] h-[520px] max-w-[96vw] max-h-[72vh] sm:max-w-[100vw] sm:max-h-[92vh]';
useEffect(() =>
{
@@ -122,7 +126,7 @@ const CatalogModernViewInner: FC<{}> = () =>
return (
<>
{ isVisible &&
<NitroCardView className="nitro-catalog w-[780px] h-[520px]" uniqueKey="catalog">
<NitroCardView className={ `nitro-catalog ${ catalogCardSize }` } uniqueKey="catalog">
<NitroCardHeaderView className={ currentType === CatalogType.BUILDER ? 'builders-club-card-header' : '' } headerText={ LocalizeText('catalog.title') } onCloseClick={ () => setIsVisible(false) } style={ buildersClubHeaderStyle } />
<NitroCardContentView classNames={ [ 'p-0!', 'overflow-hidden!' ] }>
{ /* Admin banner */ }
@@ -141,7 +145,7 @@ const CatalogModernViewInner: FC<{}> = () =>
<CatalogBuildersClubStatusView />
<div className="flex min-h-0 flex-1">
{ /* === LEFT SIDEBAR === */ }
<div className="group/rail flex flex-col w-[52px] hover:w-[175px] min-w-[52px] bg-card-grid-item border-r-2 border-card-grid-item-border py-1.5 gap-px overflow-y-auto overflow-x-hidden transition-[width] duration-200 ease-in-out">
<div className="group/rail flex flex-col w-[52px] sm:hover:w-[175px] min-w-[52px] bg-card-grid-item border-r-2 border-card-grid-item-border py-1.5 gap-px overflow-y-auto overflow-x-hidden transition-[width] duration-200 ease-in-out [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
{ /* Favorites toggle */ }
<div
@@ -279,7 +283,7 @@ const CatalogModernViewInner: FC<{}> = () =>
: <span className="text-muted">{ LocalizeText('catalog.title') }</span> }
</div>
<div className="w-[180px] shrink-0">
<div className="w-[110px] sm:w-[180px] shrink-0">
<CatalogSearchView />
</div>
@@ -301,7 +305,7 @@ const CatalogModernViewInner: FC<{}> = () =>
</div>
: <>
{ !navigationHidden && activeNodes && activeNodes.length > 0 &&
<div className="w-[170px] min-w-[170px] border-r-2 border-card-grid-item-border bg-card-grid-item overflow-y-auto py-1">
<div className="w-[120px] min-w-[120px] sm:w-[170px] sm:min-w-[170px] border-r-2 border-card-grid-item-border bg-card-grid-item overflow-y-auto py-1">
<CatalogNavigationView node={ activeNodes[0] } />
</div> }
<div className="flex-1 overflow-auto p-2 nitro-card-content-shell">
+1
View File
@@ -11,6 +11,7 @@ export const CatalogView: FC<{}> = () =>
// Default = upstream rebuilt catalog (CatalogClassicView, latest release theme).
// The "stile classico" toggle (or global catalog.classic.style flag) switches
// to the Hippiehotel.nl catalog (CatalogModernView, self-contained tailwind).
// Both the normal catalog and the Builders Club follow this toggle.
if(catalogClassicStyle) return (
<>
<div className="hidden" data-catalog-localization-version={ catalogLocalizationVersion } />
@@ -19,6 +19,13 @@ const TAB_PETS: string = 'inventory.furni.tab.pets';
const TAB_BADGES: string = 'inventory.badges';
const TAB_PREFIXES: string = 'inventory.prefixes';
const TABS = [ TAB_FURNITURE, TAB_PETS, TAB_BADGES, TAB_PREFIXES, TAB_BOTS ];
// Maps an optional link code (inventory/show/<code>) to a tab so other views
// (e.g. the profile "Change Badges" button) can deep-link to a specific tab.
const TAB_BY_CODE: Record<string, string> = {
furni: TAB_FURNITURE, furniture: TAB_FURNITURE,
pets: TAB_PETS, badges: TAB_BADGES,
prefixes: TAB_PREFIXES, bots: TAB_BOTS
};
const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.PET, UnseenItemCategory.BADGE, UnseenItemCategory.PREFIX, UnseenItemCategory.BOT ];
const TAB_ICONS: Record<string, ReactNode> = {
[TAB_FURNITURE]: <FaCouch />,
@@ -94,12 +101,14 @@ export const InventoryView: FC<{}> = props =>
{
case 'show':
setIsVisible(true);
if(parts[2] && TAB_BY_CODE[parts[2]]) setCurrentTab(TAB_BY_CODE[parts[2]]);
return;
case 'hide':
setIsVisible(false);
return;
case 'toggle':
setIsVisible(prevValue => !prevValue);
if(parts[2] && TAB_BY_CODE[parts[2]]) setCurrentTab(TAB_BY_CODE[parts[2]]);
return;
}
},
+2 -2
View File
@@ -22,7 +22,7 @@ export const NavigatorView: FC<{}> = props =>
{
const { topLevelContext, topLevelContexts, navigatorData, navigatorSearches } = useNavigatorData();
const { searchResult, isFetching } = useNavigatorSearch();
const { isVisible, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen, isOpenSavesSearches, needsInit } = useNavigatorUiState();
const { isVisible, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen, isOpenSavesSearches, needsInit, currentTabCode } = useNavigatorUiState();
const elementRef = useRef<HTMLDivElement>(null);
useNitroEvent<RoomSessionEvent>(RoomSessionEvent.CREATED, event =>
@@ -122,7 +122,7 @@ export const NavigatorView: FC<{}> = props =>
{ topLevelContexts && topLevelContexts.length > 0 && topLevelContexts.map((context, index) =>
<NitroCard.TabItem
key={ index }
isActive={ topLevelContext === context && !isCreatorOpen }
isActive={ (currentTabCode ? currentTabCode === context.code : topLevelContext === context) && !isCreatorOpen }
onClick={ () => useNavigatorUiStore.getState().setTab(context.code) }>
{ LocalizeText('navigator.toplevelview.' + context.code) }
</NitroCard.TabItem>) }
@@ -1,6 +1,6 @@
import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetUserProfile, LocalizeText } from '../../api';
import { CreateLinkEvent, GetUserProfile, LocalizeText } from '../../api';
import { Flex, LayoutAvatarImageView } from '../../common';
interface RelationshipsContainerViewProps
@@ -29,7 +29,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
</Flex>
<div className="nitro-extended-profile__relationship-copy">
<div className="nitro-extended-profile__relationship-box">
<p className="nitro-extended-profile__relationship-name" onClick={ event => (relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }>
<p className="nitro-extended-profile__relationship-name" onClick={ event => ((relationshipInfo && (relationshipInfo.randomFriendId >= 1)) ? GetUserProfile(relationshipInfo.randomFriendId) : CreateLinkEvent('friends/toggle')) }>
{ (!relationshipInfo || (relationshipInfo.friendCount === 0)) &&
LocalizeText('extendedprofile.add.friends') }
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
@@ -37,7 +37,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
</p>
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
<div className="nitro-extended-profile__relationship-head">
<LayoutAvatarImageView direction={ 4 } figure={ relationshipInfo.randomFriendFigure } headOnly={ true } classNames={ [ '!w-auto', '!h-auto', '!left-0' ] } />
<LayoutAvatarImageView direction={ 2 } figure={ relationshipInfo.randomFriendFigure } headOnly={ true } />
</div> }
</div>
<p className="nitro-extended-profile__relationship-subcopy">
@@ -60,18 +60,12 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
prefixText={ userProfile.prefixText }
username={ userProfile.username } />
<p className="nitro-extended-profile__motto">{ userProfile.motto || '\u00A0' }</p>
<p
className="nitro-extended-profile__meta"
dangerouslySetInnerHTML={ {
__html: LocalizeText('extendedprofile.created', [ 'created' ], [ userProfile.registration ])
} }
/>
<p
className="nitro-extended-profile__meta"
dangerouslySetInnerHTML={ {
__html: LocalizeText('extendedprofile.last.login', [ 'lastlogin' ], [ FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) ])
} }
/>
<p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.created').replace(/%\w+%/g, '').trim() }</b> { userProfile.registration }
</p>
<p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.last.login').replace(/%\w+%/g, '').trim() }</b> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) }
</p>
<p className="nitro-extended-profile__meta nitro-extended-profile__meta--strong">
<b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints }
</p>
@@ -100,10 +94,10 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
{ isOwnProfile &&
<div className="nitro-extended-profile__actions">
<button className="nitro-extended-profile__link" type="button">
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('avatar-editor/show') }>
{ LocalizeText('extended.profile.change.looks') }
</button>
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('inventory/open/badges') }>
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('inventory/show/badges') }>
{ LocalizeText('extended.profile.change.badges') }
</button>
</div> }
@@ -148,11 +142,11 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
<span className="nitro-extended-profile__summary-label">{ LocalizeText('inventory.badges') }</span>
<span className="nitro-extended-profile__summary-value">{ totalBadges }</span>
</button>
<div className="nitro-extended-profile__summary-button">
<button className="nitro-extended-profile__summary-button nitro-extended-profile__summary-button--center" type="button" onClick={ () => CreateLinkEvent('achievements/toggle') }>
<img className="nitro-extended-profile__summary-icon" src={ profileLevelIcon } alt="" />
<span className="nitro-extended-profile__summary-label">{ LocalizeText('extendedprofile.achievementscore') }</span>
<span className="nitro-extended-profile__summary-value">{ userProfile.achievementPoints }</span>
</div>
</button>
</div>
</div>
);
@@ -1,6 +1,6 @@
import { ExtendedProfileChangedMessageEvent, GetSessionDataManager, NavigatorSearchComposer, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { ExtendedProfileChangedMessageEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
import { CreateLinkEvent, GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
import { useMessageEvent, useNitroEvent } from '../../hooks';
import { NitroCard } from '../../layout';
import { GroupsContainerView } from './GroupsContainerView';
@@ -28,10 +28,11 @@ export const UserProfileView: FC<{}> = () =>
const onOpenRooms = () =>
{
if(userProfile)
{
SendMessageComposer(new NavigatorSearchComposer('hotel_view', `owner:${ userProfile.username }`));
}
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 }`);
};
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event =>
@@ -100,7 +101,7 @@ export const UserProfileView: FC<{}> = () =>
if(!userProfile) return null;
return (
<NitroCard className="nitro-extended-profile-window w-[521px] h-[537px]" uniqueKey="nitro-user-profile">
<NitroCard className="nitro-extended-profile-window w-[600px] h-[600px] max-w-[96vw] max-h-[92vh]" uniqueKey="nitro-user-profile">
<NitroCard.Header
headerText={ LocalizeText('extendedprofile.caption') }
onCloseClick={ onClose } />
-4
View File
@@ -142,10 +142,6 @@
border-radius: 4px;
background: var(--cat-panel);
overflow: auto;
/* Reserve the scrollbar space at all times (both edges) so the rows and
the active highlight stay centered/consistent whether or not the list
overflows into a scrollbar (e.g. many sub-pages). */
scrollbar-gutter: stable both-edges;
}
.nitro-catalog-classic-navigation-list {
+48 -2
View File
@@ -176,9 +176,15 @@
border: 0 !important;
}
& .nitro-card-accordion-set-header span {
font-size: 12px;
/* The header title is rendered by the shared <Text> component, which is a
<div> (not a <span>) — so target the div too, otherwise it keeps the app
default size and shows as oversized "testoni". */
& .nitro-card-accordion-set-header span,
& .nitro-card-accordion-set-header > div {
font-size: 12px !important;
font-weight: 700;
color: #111 !important;
line-height: 1.1;
}
& .nitro-card-accordion-set-header .fa-icon {
@@ -800,3 +806,43 @@
}
}
}
/* ------------------------------------------------------------------ *
* Flat (non-nested) overrides. The rules above live inside a nested
* `.nitro-friends { & ... }` block; these are written flat so they
* apply reliably and win by source order. They fix two things the
* nested rules didn't: the oversized/overflowing friend-list heads and
* the oversized accordion section titles ("testoni").
* ------------------------------------------------------------------ */
/* Friend-list avatar: clip a small box and centre the head, same proven
recipe as the messenger head. `inset: auto` cancels the component's
`inset-0`, otherwise the 130px head fills the row and overflows. */
.nitro-friends .friends-list-avatar {
position: relative !important;
width: 34px;
height: 36px;
flex-shrink: 0;
overflow: hidden;
}
.nitro-friends .friends-list-avatar .avatar-image {
position: absolute !important;
inset: auto !important;
left: 50% !important;
top: 56% !important;
width: 54px !important;
height: 54px !important;
margin: 0 !important;
background-position: center center !important;
transform: translate(-50%, -50%) scale(.95) !important;
}
/* Accordion section titles are rendered by <Text> (a <div>, not a <span>),
so size the div too — otherwise they show oversized. */
.nitro-friends .nitro-card-accordion-set-header > div,
.nitro-friends .nitro-card-accordion-set-header span {
font-size: 12px !important;
font-weight: 700 !important;
line-height: 1.15 !important;
}
+34 -26
View File
@@ -1,7 +1,3 @@
.nitro-extended-profile-window {
border-radius: 0 !important;
}
.nitro-extended-profile-window .nitro-card-header-shell {
min-height: 34px;
max-height: 34px;
@@ -46,32 +42,31 @@
.nitro-extended-profile__identity {
display: grid;
grid-template-columns: 56px minmax(0, 1fr);
gap: 8px;
grid-template-columns: 68px minmax(0, 1fr);
gap: 10px;
}
/* Mirror the room infostand exactly: a 68x135 flex column (= profile-background)
that centres the avatar horizontally and clips it; the stand/overlay sit on
top as absolute layers. The avatar keeps its component default classes
(relative w-[90px] h-[130px] left-[-2px]) so it lines up with bg + stand and
isn't crooked. Do NOT absolutely position or force width/height on it. */
.nitro-extended-profile__avatar-shell {
width: 56px;
height: 113px;
width: 68px;
height: 135px;
position: relative;
overflow: hidden;
background-size: cover;
background-position: center;
border-radius: 3px;
display: flex;
flex-direction: column;
align-items: center;
}
.nitro-extended-profile__avatar-stand,
.nitro-extended-profile__avatar-overlay {
position: absolute;
inset: 0;
}
.nitro-extended-profile__avatar-image {
position: absolute !important;
left: 50% !important;
bottom: -4px;
transform: translateX(-50%);
width: auto !important;
height: auto !important;
top: 0;
left: 0;
}
.nitro-extended-profile__identity-copy {
@@ -253,14 +248,27 @@
.nitro-extended-profile__relationship-head {
position: absolute;
right: -2px;
right: 3px;
top: 50%;
width: 34px;
height: 34px;
width: 30px;
height: 32px;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* Same proven recipe as the messenger head: clip a small box and centre a
54x54 avatar in it. `inset: auto` cancels the component's `inset-0` so the
width/position take effect (otherwise the head overflows huge). */
.nitro-extended-profile__relationship-head .avatar-image {
position: absolute !important;
inset: auto !important;
left: 50% !important;
top: 54% !important;
width: 50px !important;
height: 50px !important;
margin: 0 !important;
background-position: center center !important;
transform: translate(-50%, -50%) scale(.95) !important;
}
.nitro-extended-profile__relationship-subcopy {