From f7e78674c697378329e4083607bcced24a01a5df Mon Sep 17 00:00:00 2001 From: medievalshell Date: Sat, 30 May 2026 05:49:04 +0200 Subject: [PATCH] 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/ 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) --- src/components/catalog/CatalogClassicView.tsx | 3 +- src/components/catalog/CatalogModernView.tsx | 12 ++-- src/components/catalog/CatalogView.tsx | 1 + src/components/inventory/InventoryView.tsx | 9 +++ src/components/navigator/NavigatorView.tsx | 4 +- .../RelationshipsContainerView.tsx | 6 +- .../user-profile/UserContainerView.tsx | 26 ++++---- .../user-profile/UserProfileView.tsx | 15 ++--- src/css/catalog/CatalogClassicView.css | 4 -- src/css/friends/FriendsView.css | 50 +++++++++++++++- src/css/user-profile/UserProfileView.css | 60 +++++++++++-------- 11 files changed, 125 insertions(+), 65 deletions(-) diff --git a/src/components/catalog/CatalogClassicView.tsx b/src/components/catalog/CatalogClassicView.tsx index 1ee04f2..9df060e 100644 --- a/src/components/catalog/CatalogClassicView.tsx +++ b/src/components/catalog/CatalogClassicView.tsx @@ -252,7 +252,8 @@ const CatalogClassicViewInner: FC<{}> = () =>
- { !!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) && }
diff --git a/src/components/catalog/CatalogModernView.tsx b/src/components/catalog/CatalogModernView.tsx index 1c4af9a..ea1c2de 100644 --- a/src/components/catalog/CatalogModernView.tsx +++ b/src/components/catalog/CatalogModernView.tsx @@ -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 && - + setIsVisible(false) } style={ buildersClubHeaderStyle } /> { /* Admin banner */ } @@ -141,7 +145,7 @@ const CatalogModernViewInner: FC<{}> = () =>
{ /* === LEFT SIDEBAR === */ } -
+
{ /* Favorites toggle */ }
= () => : { LocalizeText('catalog.title') } }
-
+
@@ -301,7 +305,7 @@ const CatalogModernViewInner: FC<{}> = () =>
: <> { !navigationHidden && activeNodes && activeNodes.length > 0 && -
+
}
diff --git a/src/components/catalog/CatalogView.tsx b/src/components/catalog/CatalogView.tsx index 600264b..87344fe 100644 --- a/src/components/catalog/CatalogView.tsx +++ b/src/components/catalog/CatalogView.tsx @@ -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 ( <>
diff --git a/src/components/inventory/InventoryView.tsx b/src/components/inventory/InventoryView.tsx index 4c95c35..881ddce 100644 --- a/src/components/inventory/InventoryView.tsx +++ b/src/components/inventory/InventoryView.tsx @@ -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/) 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 = { + 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 = { [TAB_FURNITURE]: , @@ -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; } }, diff --git a/src/components/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx index b93b8d5..8daf79b 100644 --- a/src/components/navigator/NavigatorView.tsx +++ b/src/components/navigator/NavigatorView.tsx @@ -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(null); useNitroEvent(RoomSessionEvent.CREATED, event => @@ -122,7 +122,7 @@ export const NavigatorView: FC<{}> = props => { topLevelContexts && topLevelContexts.length > 0 && topLevelContexts.map((context, index) => useNavigatorUiStore.getState().setTab(context.code) }> { LocalizeText('navigator.toplevelview.' + context.code) } ) } diff --git a/src/components/user-profile/RelationshipsContainerView.tsx b/src/components/user-profile/RelationshipsContainerView.tsx index 4de625a..204eb2b 100644 --- a/src/components/user-profile/RelationshipsContainerView.tsx +++ b/src/components/user-profile/RelationshipsContainerView.tsx @@ -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 = p
-

(relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }> +

((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 = p

{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
- +
}

diff --git a/src/components/user-profile/UserContainerView.tsx b/src/components/user-profile/UserContainerView.tsx index 73b3ca2..e4e6b43 100644 --- a/src/components/user-profile/UserContainerView.tsx +++ b/src/components/user-profile/UserContainerView.tsx @@ -60,18 +60,12 @@ export const UserContainerView: FC = props => prefixText={ userProfile.prefixText } username={ userProfile.username } />

{ userProfile.motto || '\u00A0' }

-

-

+

+ { LocalizeText('extendedprofile.created').replace(/%\w+%/g, '').trim() } { userProfile.registration } +

+

+ { LocalizeText('extendedprofile.last.login').replace(/%\w+%/g, '').trim() } { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) } +

{ LocalizeText('extendedprofile.achievementscore') } { userProfile.achievementPoints }

@@ -100,10 +94,10 @@ export const UserContainerView: FC = props => { isOwnProfile &&
- -
} @@ -148,11 +142,11 @@ export const UserContainerView: FC = props => { LocalizeText('inventory.badges') } { totalBadges } -
+
+
); diff --git a/src/components/user-profile/UserProfileView.tsx b/src/components/user-profile/UserProfileView.tsx index fd5a3b5..801fd4d 100644 --- a/src/components/user-profile/UserProfileView.tsx +++ b/src/components/user-profile/UserProfileView.tsx @@ -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, event => @@ -100,7 +101,7 @@ export const UserProfileView: FC<{}> = () => if(!userProfile) return null; return ( - + diff --git a/src/css/catalog/CatalogClassicView.css b/src/css/catalog/CatalogClassicView.css index dc99550..48a6100 100644 --- a/src/css/catalog/CatalogClassicView.css +++ b/src/css/catalog/CatalogClassicView.css @@ -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 { diff --git a/src/css/friends/FriendsView.css b/src/css/friends/FriendsView.css index 2b2ea17..3786920 100644 --- a/src/css/friends/FriendsView.css +++ b/src/css/friends/FriendsView.css @@ -176,9 +176,15 @@ border: 0 !important; } - & .nitro-card-accordion-set-header span { - font-size: 12px; + /* The header title is rendered by the shared component, which is a +
(not a ) — 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 (a
, not a ), + 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; +} diff --git a/src/css/user-profile/UserProfileView.css b/src/css/user-profile/UserProfileView.css index 3f3e47b..66bbec6 100644 --- a/src/css/user-profile/UserProfileView.css +++ b/src/css/user-profile/UserProfileView.css @@ -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 {